0

In a related question, I asked how to get the value of the max element in a vector of objects in c++ based o some field of said objects. I extended the algorithm to get the index of that max element so I could later just pop it from the vector of object.

Here is the code that I use right now and it works fine:

vector<MyOwnClass> scoretracker // MyOwnClass has a ".score" field

// Some code filling scoretracker

auto max = std::max_element(scoretracker.begin(), scoretracker.end(),
    [] (const MyOwnClass &a, const MyOwnClass &b )
{
    return a.score < b.score; // I am not sure how this line works
});

int index = distance(scoretracker.begin(), max);

So, I tried to modify this to get the second highest (or n-th value) instead of the max but so far my attempts failed.

It makes me realize that I don't really understand why "return a.score < b.score" returns the highest value.

By looking at how max_element works, I am not sure if it could ever be used to find the 2nd largest.

Oh, finally, I would rather not pop_back the highest value from the vector, find the new max (the 2nd highest value in the original vector) and add some logic to restore the original version. Well if I have to, I'll do it but there might be some iterator property or something else I don't know...

Community
  • 1
  • 1
Doombot
  • 553
  • 3
  • 8
  • 18
  • 4
    Instead of `max_element`, you can just `std::sort`. Then you can just find the 1st, 2nd, or nth highest element just by indexing the sorted array (or a sorted copy if you want). – Cory Kramer Nov 13 '14 at 16:42
  • Ok I am trying some implementation right now! – Doombot Nov 13 '14 at 16:44
  • 1
    `why "return a.score < b.score" returns the highest value` That is a predicate to order the values. What is done *after* the values are ordered is up to the algorithm. – PaulMcKenzie Nov 13 '14 at 16:47
  • @Cyber Ok I missed the last couple of words from your comment. If one does not want to break the ordering of the vector, simply sort a copy. I will look at how much time it eats in my process. Thanks! – Doombot Nov 13 '14 at 16:53
  • Is this a duplicate of http://stackoverflow.com/questions/251781/how-to-find-the-kth-largest-element-in-an-unsorted-array-of-length-n-in-on – Pace Nov 13 '14 at 17:11
  • Well if you look at the original question, I did not know at that time that it was a similar type of problem than the one in this possible duplicate. I thought I would have to modify max_elements... Well if this is really a duplicate someone could cast a duplicate vote, or maybe the mention at the beginning could be enough... – Doombot Nov 13 '14 at 18:41
  • But what I like from the present question's answers is that they provide code adapted to c++, and they do not (necessarily) bother with keeping a O(n) complexity. So that's why I think this is not a duplicate, but it is only my humble opinion... – Doombot Nov 13 '14 at 18:43

4 Answers4

4

If you need the index of only once (and not for all values), you may use:

std::size_t get_index_of_nth_greatest(const std::vector<MyOwnClass>& v, std::size_t k)
{
    std::vector<std::size_t> indexes(v.size());
    std::iota(indexes.begin(), indexes.end(), 0);

    std::nth_element(indexes.begin(), indexes.begin() + k, indexes.end(),
        [&](int lhs, int rhs)
        {
            return v[lhs].score > v[rhs].score;
        }
    );
    return indexes[k];
}

Live example.

Note: As Vlad from Moscow points out, with duplicate inputs, there is no guaranty of the order of the duplicates, and so you may have identical indexes for different k.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I knew of `partial_sort`, so I figured something akin to `nth_element` must exist. – Mooing Duck Nov 13 '14 at 17:29
  • @Jarod42 It si a wrong solution because in general case elements of the cntainer can coincide. – Vlad from Moscow Nov 13 '14 at 17:32
  • @VladfromMoscow: The same index may be valid for several `k` (with duplicate) even if it is not intuitive (BTW, I got distinct indexes in this [example](http://coliru.stacked-crooked.com/a/ac10e48e07a9cec5)). – Jarod42 Nov 13 '14 at 17:40
  • @Jarod42 I have not understood what you wrote however please answer the qestion what will be returned for sequence { 2, 2, 2, 1 } and k equal to 2? – Vlad from Moscow Nov 13 '14 at 17:44
  • @VladfromMoscow: Only for `k = 3` the expected index would be `3` with value `=1`. For k < 3, any index in `{0, 1, 2}` would be acceptable for me (with value `=2`). – Jarod42 Nov 13 '14 at 18:01
  • @Jarod42 So we may resume that your solution is wrong. – Vlad from Moscow Nov 13 '14 at 18:03
2

try this:

auto max = std::sort(scoretracker.begin(), scoretracker.end(),
    [] (const MyOwnClass &a, const MyOwnClass &b )
{
    return a.score < b.score;
});

then

scoretracker.back().score;

would give you last element

scoretracker.at(position).score;

would return element of the position where position can be any number

Rupesh Yadav.
  • 894
  • 2
  • 10
  • 24
  • He does not have to sort whole sequence to get nth highest element, `std::nth_element` is sufficient – Slava Nov 13 '14 at 17:28
  • OMG is it available?? waoo, i dont know when m gona see everything now c++ provides. BTW thanks @Slava – Rupesh Yadav. Nov 13 '14 at 17:41
  • Since this (std::sort) work in my case and that I am not able to use nth_element correctly, I will eventually mark this as a correct answer. But if someone shows me a working example with nth_element and that it is faster in my code, I will choose the fastest ^^ – Doombot Nov 13 '14 at 20:32
1

If you want a more sophisticated approach you can look at the std::partition function.

std::partition takes a container and divides it into two parts.

std::vector<MyOwnClass> v(100);
// fill vector with stuff. Find n-th element.
auto mid = v[somwhere_in_the_middle];
auto p = std::partition(v.begin(), v.end(), 
                [mid](MyOwnClass v){ return v.score < mid.score; } );

Every element bigger than p is to the right of p. The smaller ones are to the left. If you are looking for the second biggest, you go to the right as long as the distance of v.end() - p is large enough.

This method is called quick-select, based on the ideas of quicksort, and is detailed here How to find the kth largest element in an unsorted array of length n in O(n)?.

And this is of course already implemented as std::nth_element and can be used as

std::nth_element(v.begin(), v.begin()+5, v.end(), std::greater<int>());

To get the 6th largest element at the 6th position.

Community
  • 1
  • 1
Captain Giraffe
  • 14,407
  • 6
  • 39
  • 67
  • Well reading on nth_element, this sounds like what I want. From what I understand, the `+5` denotes the position (counting from 0) of the wanted item. `auto p = std::nth_element(scoretracker.begin(), scoretracker.begin()+1, scoretracker.end(), std::greater() );` Strangely, there is an error saying that there is no member "greater" in std, which is seemingly no true (VS2012Pro, C++) – Doombot Nov 13 '14 at 18:35
  • Ok I should add #include http://stackoverflow.com/questions/16567699/stdgreater-not-defined-in-msvc2012 – Doombot Nov 13 '14 at 18:44
  • @Doombot Yes, you need to give it the same comparison lambda as you are currently using. It does not know about your score member. Include too. – Captain Giraffe Nov 13 '14 at 18:45
  • Ok what about the `somwhere_in_the_middle` from the partition code, whatever value I seem to use it returns the same thing into p. Also, I tried to fit the comparison lambda different ways but it all fails. So this mean I don't understand the lambda... -_- – Doombot Nov 13 '14 at 19:21
  • OK now I used this: `std::nth_element(scoretracker.begin(), scoretracker.begin()+1, scoretracker.end(), [] (const MyOwnClass &a, const MyOwnClass &b ) { return a.score > b.score; });` and it sorts scoretracker from the biggest score to the lowest. It will be fine but it is like if I used sort? – Doombot Nov 13 '14 at 19:50
  • @Doombot Almost. It orders the minimal sequence required. A sort is always going to be slower. If I understand you correctly. – Captain Giraffe Nov 13 '14 at 20:31
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/64893/discussion-between-doombot-and-captain-giraffe). – Doombot Nov 13 '14 at 20:37
0

You can do it by taking the max element you already have and looking for the std::max_element() either side of it and getting the std::max() of those two values:

struct MyOwnClass
{
    int score = 0;
    MyOwnClass(int s): score(s) {}
};

vector<MyOwnClass> scores = {{4}, {2}, {7}, {9}, {3}}; 

int main()
{
    // we need this more than once
    auto compare = [] (const MyOwnClass &a, const MyOwnClass &b)
    {
        return a.score < b.score; // I am not sure how this line works
    };

    auto max = std::max_element(scores.begin(), scores.end(), compare);

    std::cout << "max: " << max->score << '\n';

    // call std::max_element() twice with ranges either side of the max element
    // and get the std::max() of those values
    auto next_max = std::max(std::max_element(scores.begin(), max, compare)->score
        , std::max_element(max + 1, scores.end(), compare)->score);

    std::cout << "next max: " << next_max << '\n';
}

NOTE: If you want to get the third or fourth elements this technique is a technique of diminishing returns, probably better to do a std::sort().

Galik
  • 47,303
  • 4
  • 80
  • 117