2

I am still a novice at PHP scripting.

I have an Array

$students = array(1201=>94,1203=>94,1200=>91, 1205=>89, 1209=>83, 1206=>65, 1202=>41, 1207=>38,1208=>37, 1204=>37,1210=>94);

From the associative array, the key are the student's exam no and the values are the student's scores. Then I used the 2 inbult PHP functions array_keys and array_values to separate the exam nos from the scores.

$exam_nos=(array_keys($students)); 
$marks=(array_values($students));

Then I passed the $marks array through the code below:

 $i=0;
$occurrences = array_count_values($marks);
$marks = array_unique($marks);
echo '<table border="1">';
foreach($marks as $grade) {
  if($grade == end($marks))$i += $occurrences[$grade]-1;
  echo str_repeat('<tr><td>'.$grade.': '.($i+1).'</td></tr>',$occurrences[$grade]);
  $i += $occurrences[$grade];
}
echo '</table><br />';

output:

 94: 1
 94: 1
 94: 1
 91: 4
 89: 5
 83: 6
 65: 7
 41: 8
 38: 9
 37: 11
 37: 11

And this is closer to what I want; to rank the scores such that if a tie is encountered, 1 or more positions are skipped, occurs at the end the position the items at the end are assigned a position equivalent toi the total number of ranked items. However, it would be much helpful if this could be done without separating the Array into 2 ... Questions: (1) I am pulling my hair how, from the $student array I could have something like:

Exam No Score Position
 1201     94      1
 1210     94      1
 1203     94      1
 1200     91      4
 1205     89      5
 1209     83      6
 1206     65      7
 1202     41      8
 1207     38      9
 1204     37      11
 1208     37      11

(2) I would like to be able to pick any student by exam no and be able to echo or print out her position e.g

the student 1207 is number 9.

I think I need to capture the postions in a variable, but how do I capture them? Well I don't know!

Could the experts help me here with a better way to achieve my 2 goals (please see questions 1 and 2)? I will try any suggestion that will help me disolve the 'metal blockage' I have hit.

Kal2001
  • 27
  • 1
  • 9
  • Where are you getting your data from (for example a database?) - you could let the database do all the work for you - Oracle, for example, has some very nice analytical queries where you can determine a rank using a partition by statement (one of my earlier questions when I had an issue getting MySQL to do the same) - http://stackoverflow.com/questions/24953173/mysql-how-to-do-an-oracle-rank-overorder-by-score-desc – ash Feb 07 '15 at 18:09
  • I am working with arrays and some data from MySQL which I fetch as arrays. I found that the database can do simple ranking but I could not figure iout how to do the complicated ranking using inbuilt MySQL functions. aLSO, A score may be an sum of 2 scores from 2 different tables which I add up and pack in an array at query time so I just can't find a way for the database to do the work that is why I am looking at writing PHP functions to process the data. – Kal2001 Feb 07 '15 at 18:32
  • @Kal2001 there is probably away to do this in pure SQL so your data comes out properly... you might want to post a second question including a `SHOW CREATE TABLE` for all the tables invloved and then instructions on how the SUMing and ranking works, as well as the problems you ran in to. – prodigitalson Feb 07 '15 at 18:55
  • @prodigitalson, thanks very much for your contribution. I am sure there is a way, just that my SQL has not reached that level where I can approach this problem from the SQL angle. That is not ruled out, I will certainly go that route as I upgrade my SQL understanding. If you can give some pointers, you will get me started. Once again thanks. – Kal2001 Feb 08 '15 at 18:05
  • @Kal2001 Id be happy to give you pointers/explain, but I do not have enough info... Id need your table structure and some info on what the calculations need to be. As i Said if you post a second question with these details I and others would be happy to give you some info. – prodigitalson Feb 08 '15 at 18:18

2 Answers2

4

If you're pulling out the students from a database (mentioned in the comments), you could retrieve them with the desired format directly using SQL.

However, I'm going to assume that that's not an option. You could do as follows:

$students = array(1201=>94,1203=>94,1200=>91, 1205=>89, 1209=>83, 1206=>65, 1202=>41, 1207=>38,1208=>37, 1204=>37,1210=>94);
arsort($students);// It orders high to low by value. You could avoid this with a simple ORDER BY clause in SQL.

$result = array();
$pos = $real_pos = 0;
$prev_score = -1;
foreach ($students as $exam_n => $score) {
    $real_pos += 1;// Natural position.
    $pos = ($prev_score != $score) ? $real_pos : $pos;// If I have same score, I have same position in ranking, otherwise, natural position.
    $result[$exam_n] = array(
                     "score" => $score, 
                     "position" => $pos, 
                     "exam_no" => $exam_n
                     );
    $prev_score = $score;// update last score.
}

$desired = 1207;
print_r($result);
echo "Student " . $result[$desired]["exam_no"] . ", position: " . $result[$desired]["position"] . " and score: ". $result[$desired]["score"];

Hope it helps you.

acontell
  • 6,792
  • 1
  • 19
  • 32
  • thank you very much. This is definitely answering the question except when 2 or more students tie at the last position. In our example, there are 11 students, and we have at the end, 1207 38 9 1204 37 11 1208 37 11 – Kal2001 Feb 08 '15 at 18:11
  • Thank you very much. This is definitely answering the question except when 2 or more students tie at the last position. In our example, there are 11 students, at the end the ranking should be as follows 1207 38 9 1204 37 11 1208 37 11 So there wont be # 10 as, a gap is left and that gap must be 1 less than the number of people at the last position. So if, we had 38, 37,37,37, and the total # of students was 12, we would have 38 9 37 12 37 12 37 12 – Kal2001 Feb 08 '15 at 18:22
  • nevertheless, your answer is great as I have been able to isolate a particular student's score using your code, even when, I am getting the data from MySQL, all is okay except the small inaccuracy that occurs if students tie at the end, please see the Standard ranking from the question, paying attention to the very last 2 position, where a gap has been left. I am fiddling with it and hope I will succeed. Thank you very much. – Kal2001 Feb 08 '15 at 18:27
  • @Kal2001 hi there, wouldn't it be more accurate if both of them had position 10? why position 11? – acontell Feb 08 '15 at 18:32
  • @Kal2001 I've been checking the documentation you provide and the code you mentioned in some of the comments and I don't think the standard ranking ranks 11 the last two ones. It would rank them 10. Try it with the example in https://gist.github.com/leemachin/3859792. If you rank 1-2-2-2-5 the last is five as it should be. – acontell Feb 08 '15 at 19:32
  • The Standard Ranking must assign the least scorer the lowest posistion possible. If no tie is present, you would have 1,2,3,4,5,6,7,8,9,10,11. In our example, it is 1,1,1,4,5,6,7,8,9,11,11. Because a tie exists at position 1, 2 positions are skipped. At the end, one position is skiped (position 10) which is 1 less than the number of the people tiying at the last position. There are 11 students so last position is 11. If you end at 10, it looks like only 10 students were competing. Don't you think 1,11,11,11,11,11,11,11,11,11,11 will be more accurate (wierd though) than 1,2,2,2,2,2,2,2,2,2,2? – Kal2001 Feb 08 '15 at 19:44
  • @Kal2001 absolutely not, standard ranking states that your ranking depends on the number of people that have scored more than you. Therefore, if only one person has scored more than me, I should come second, independently of the amount of people that have my same score. The code provided in https://gist.github.com/leemachin/3859792 follows this policy. 1-11-11-11-11 doesn't make any sense, 1-2-2-2-2 does. – acontell Feb 08 '15 at 19:47
  • I am positive we all got it wrong from this end. You make perfect sense. You must right so I guess that settles it. Thanks, you have been of much much help. – Kal2001 Feb 08 '15 at 21:35
1

I would use a custom object to process the students individually and store them in an array.

$students = array(1201=>94,1203=>94,1200=>91, 1205=>89, 1209=>83, 1206=>65, 1202=>41, 1207=>38,1208=>37, 1204=>37,1210=>94);
arsort($students); // Sort the array so the higher scores are on top.

$newStudents = array();
$pos = 0;
$count = 0;
$holder = -1; // Assuming no negative scores.
foreach($students as $k=>$v){
    $count++; // increment real counter
    if($v < $holder || $holder == -1){
        $holder = $v;
        $pos = $count;
    }

    $newStudents[] = makeStudent($pos, $v, $k);
    // If you want the exam # as the array key.
    // $newStudents[$k] = $student;
}

$newStudents = fixLast($newStudents);

// outputs
print_r($newStudents);

foreach($newStudents as $v){
   echo "position : " . $v->position . "<br>";
   echo "score : " . $v->score . "<br>";
   echo "exam : " . $v->exam . "<br>";
}


function makeStudent($pos, $score,$examNo){
    $student = new stdClass(); // You could make a custom, but keeping it simple
    $student->position = $pos;
    $student->score = $score;
    $student->exam = $examNo;

    return $student;
}

function fixLast($students){
    $length = count($students) -1;
    $count = 0;
    $i = $length;
    while($students[$i]->position == $students[--$i]->position){
        $count++;
    }

    for($i = 0; $i <= $count; $i++){
        $students[$length - $i]->position = $students[$length - $i]->position + $count;
    }
    return $students;
}
Charles Forest
  • 1,035
  • 1
  • 12
  • 30
  • Thanks for the help. There are 3 scores at no. 1. With you code, they are numbered as follows: 94: 1, 94: 2, 94:3. That's in incorrect. all these scores must have the same position, please see the question again for the correct / desired ranking – Kal2001 Feb 07 '15 at 18:37
  • Filled with thumbs today. I changed it a little bit. @acontell has a good answer too. I gave you a version without the ternary operator since you are a novice and it can get confusing. – Charles Forest Feb 07 '15 at 19:14
  • Had a major power failure due to lightening so I was off net for over 10 hours. – Kal2001 Feb 08 '15 at 12:01
  • Thanks once again. This is coming up. I am now struggling to modify it to be able to handle a tie at the end. It is giving the 2 scores at the end the 10th Position. That is incorrect, the correct numbering is such that the last position is always egual to the total number of students except if all the students have scored the 1st position. So from the 9th position, the 2 students at the end of the ranking will both jump to number 11. Number 10 is forfeited since the last scorers both got the same mark. (FYI: Had a major power failure due to lightening so I was off net for over 10 hours.) – Kal2001 Feb 08 '15 at 12:09
  • I fixed it, but you should know that you're putting a bias in the way you're calculating your results. (The students won't be happy to see that.) – Charles Forest Feb 08 '15 at 14:22
  • Forester, I was also baffled when I was told the ranking should be like this that I argued it was wrong. So I googled and found that, they have adopted something WIKIPEDIA (http://en.wikipedia.org/wiki/Ranking), calls Standard Ranking, also explained here https://gist.github.com/leemachin/3859792. However I agree with you, it looks biased since a student ends up getting a rank way down even though they are 1 mark less than the one immediately above them. – Kal2001 Feb 08 '15 at 18:36
  • Forester, You sir are a Genius, it ranks everything according to Standard Ranking regardless of any ties! Now you used something OOP, I am still , so now I am wondering how to extract a particular student's score if her Exam no is known. I see the exam no, post, score in some class, but I am not familiar with OOP. So how do I isolate a position for any particular student and print or echo it out? – Kal2001 Feb 08 '15 at 19:09
  • Well, the good answer to the 2nd part would be to make a custom Student class for the object instead of the stdClass and accelerate the looping through recursive or chained functions. If you're new to the concept I'd suggest to just keep it to foreach loops and make functions to search each statements and do some code revision once you have free time. – Charles Forest Feb 08 '15 at 20:20
  • I think you were right the first time in terms of ranking so I will go with your earlier answer even though I still will use the second for learning purposes. So the first part is answered. Thanks very much, I guess you have done your part. I wish I had the necessary reputation to up-vote it. – Kal2001 Feb 08 '15 at 21:39