I am retrieving data from mysql whick=h I need to rank from highest to lowest. I am thinking of retreaving it as a single array by using a user-defined function. The ranking becomes tricky if there are duplicates. Let me illustrate with an example;
89, 79, 79, 79, 78, 54, 50, 41, 41, 38. The above would be ranked as;

89 - 1, 79 -2, 79 - 2, 79 - 2, 78 - 5, 54 - 6, 50 - 7, 41 - 8, 41 - 8, 38 - 10
it becomes more tricky if a tie occurs at the end, such as;

89 - 1, 79 -2, 79 - 2, 79 - 2, 78 - 5, 54 - 6, 50 - 7, 41 - 8, 38 - 10, 38 - 10

So as you can see, if there is a tie, the numbers tying are assigned the number, and the next position is skipped to preserve postions because at the end of the day, the number of positions should equal the number of scores, as illustrated above.

My question is, is it possible to use PHP to that kind of ranking assuming we have the scores as an array? Can someone help me write a function that can do this and which is robust enough to handle duplicate values in the manner illustrated above?

Could it be that PHP CANNOT do this kind of manipulation?

    Well the first thing to do would be to make sure the data is sorted in descending order when you retrieve it from MySQL (which it looks like you've already done?). I'm also assuming that you've stored those sorted values in a numerically indexed array.

    What I'd probably do is use a [man]for/man loop with two counter variables; $i and $rank. Initialize $i to 0 (this will be our array index pointer so we can visit each position in the array of values - from 0 to (length of array minus 1), incremented by 1 after every iteration of the for() loop). Also initialize $rank to 1 (since the first rank would be 1st place).

    When $i is 0, you've got a special case - the first value is always 1 and no comparison is done. Simply assign it a rank of 1. How to do that? Well, I'd probably maintain a second array - $ranks - and use the same numerical indexes ($i in our loop) as the original data array. In other words, $data[3] would be the fourth value retrieved from MySQL, and $ranks[3] would tell you which rank/place that value was placed in.

    Next, you've got another special case to check for. If the current value matches the previous value in the data array, then you should assign the same rank to it as you did the previous value. In other words, don't increment the $rank counter in the for() loop - just use the same value as before.

    You've also got one more special case to check for; if the current value matches the last value in the array, then we've got a tie for last place. In this case, you need to figure out what "place" that is according to your logic shown above. Can you think of how to do this mathematically? (Hint: It's a simple expression involving one subtraction and one addition operation 😉).

    If none of those special cases are met, then you'd simply increment the $rank counter and assign the new value to the current rank.

    Try writing the code that does all of that and post what you come up with (whether it's complete or not).

    bululu wrote:

    Could it be that PHP CANNOT do this kind of manipulation?

    Since you're just talking about data manipulation and basic math, I'd hazard a guess that no popular/formal/whatever programming language exists that is incapable of doing this.

      Thank you so much for a quick response. I will do as advised tonight and will post my findings tomorrow.

        There is actually a simpler solution: make use of [man]array_count_values/man to obtain a mapping of scores to their counts. Thus, you start with a rank of 1, and then add to the result array the number of score values with that rank. Then, you increase the rank by that number of scores, and move on to the next score value. No special casing is required, though you still would be using an extra array (i.e., the result of array_count_values()).

        EDIT:

        bululu wrote:

        it becomes more tricky if a tie occurs at the end, such as;

        89 - 1, 79 -2, 79 - 2, 79 - 2, 78 - 5, 54 - 6, 50 - 7, 41 - 8, 38 - 10, 38 - 10

        So as you can see, if there is a tie, the numbers tying are assigned the number, and the next position is skipped to preserve postions because at the end of the day, the number of positions should equal the number of scores, as illustrated above.

        You need to be clear as to what you are talking about: the number of positions cannot equal the number of scores unless the score values are unique. In your first example, there are 7 positions: 1, 2, 5, 6, 7, 8 and 10, yet there are 10 scores. Consequently, it seems to me that your example with the special case for last place is a conceptual mistake in the requirements.

          Thank you. You are right my statement was misleading. I meant to say that we should have 1 to 10 positions though in actual sense some positions will not be occupied by a score. The lowest score with or wthout tying must be assigned a position equal to the number of scores since it's the lowest.

          There is no mistake, that is how pupils should be graded. I was a school tutor so we did that all the time. Imagine you have ranked your students after a test. The lowest scorers have a tie and you rank them at number 7 in a class that has 10 students ... and in most cases you rank like 1/10, 2/10, 7/10. The 7/10 indicates the scorer is not the lowest, that there is someone after that. On the other hand by giving the the lowest postion, it is clear they are the list, and if the are 3, we know there was no number 8 and 9, just like there would not be number 3 and 4 if there was a tie at number 2. A good rule of thumb is that if at the end, the tying scores are assigned the position the laqst score should have assumed. However if that happens anywhere else, the tying scores are assigned the position the first of the tying scores should have assumed.

            bululu wrote:

            I meant to say that we should have 1 to 10 positions though in actual sense some positions will not be occupied by a score. The lowest score with or wthout tying must be assigned a position equal to the number of scores since it's the lowest.

            Ah, so there is an inherent special case. If you use my suggestion, this special case is easily handled by using [man]array_pop/man on the array returned by array_count_values(). You can then continue with the algorithm as usual, and after the main loop is done, you repeatedly add, based on the count that was array_pop()ed, the last score with the last possible position.

              Write a Reply...