Sorting an array

I’m ranking users by a numerical criteria using asort(), then displaying them on a page. The array key is the user’s ID and the value is their rank. This is working just fine, however, what if I run into multiple users with the same rank - who have tied each other? I know this should be relatively easy, but I can’t get my brain unstuck to see the answer.

asort($user_rankings, SORT_NUMERIC);


// assign a numerical value (rank order) starting at 1
$i = 1;
foreach($user_rankings as $key=>$value) {
    $rankings[$key] .= $i;
    $i++;
}

Here’s the $user_rankings array after print_r($user_rankings);

Array
(
     [9462] => 1
     [9461] => 2
)

Why are you using a concatenation? The .=

I’m also unsure what you’re wanting to do

OK, let’s say I have 4 users, and I want to rank them by weight.

Tom = 150 lbs
Ann = 110 lbs
Art = 170 lbs
Ted = 150 lbs

As you can see, 2 users have the same weight, so, in essence, they’re tied for the same position on the list. Just using a sorting function it would look like this (sorting from lightest to heavest)

  1. Ann
  2. Tom
  3. Ted
  4. Art

But, since Tom and Ted weigh the same, they are both in the #2 position. So the list should really look like this, which is what I’m having difficulty acheiving.

  1. Ann
  2. Tom
  3. Ted
  4. Art

Does that make sense?

Here is a not fully tested guess at it:


<?php

	function rankByValue( array $array ) {
	
		$userKeys  = array_keys($array);
		$dupValues = array_count_values($array);
		$unqValues = array_values(
						array_unique($array)
					 );

		$arr = array();
		for( $i = 0; $i < count($unqValues); $i++ ) {
			if( isset($unqValues[$i]) ) {
				if( $dupValues[$unqValues[$i]] > 1 ) {
					$arr[] = ($i + 1);
				}
				$arr[] = ($i + 1);
			}
		}

		foreach($arr as $k => $v ) {
			$array[$userKeys[$k]] = $v;
		}		
		return $array;
	}

	$users = array(
		'Tom' => 150,
		'Ann' => 110,
		'Art' => 170,
		'Ted' => 150,
		'Bob' => 145,
		'Arnold' => 110
	);
	asort($users);

?>
<!DOCTYPE html>
<html>
	<head>
		<style>
			li { list-style-type: none; }
		</style>
	</head>
	<body>
		<ul>
			<?php
				foreach( rankByValue($users) as $k => $v ) {
					echo '<li>' .$v .'. ' . $k .'</li>';
				}
			?>		
		</ul>
	</body>
</html>

Ends up giving me:

    1. Arnold
    1. Ann
    2. Bob
    3. Tom
    3. Ted
    4. Art

I added a few ‘users’ to test out the code.

Another stab:


<?php
function rank_by_values($array)
{
  if (count($array) < 2)
    return $array;
  
  asort($array);
  $keys   = array_keys($array);
  $prev   = $array[$keys[0]];
  $i      = 1;
  $return = array();
  foreach($array as $key => $value)
  {
    if ($value != $prev)
      $i++;
    $return[$key] = $i;
  }
  return $return;
}


$users = array(
  'Tom' => 150,
  'Ann' => 110,
  'Art' => 170,
  'Ted' => 150,
  'Bob' => 145,
  'Arnold' => 110
);

var_dump(rank_by_values($users));
/*
array
  'Arnold' => int 1
  'Ann' => int 1
  'Bob' => int 2
  'Tom' => int 3
  'Ted' => int 4
  'Art' => int 5
*/

:slight_smile:

Thanks guys, that’s perfect! :slight_smile:

@ScallioXTX; - nice!

Aaaaaaaad, the plot thickens. Each person belongs to a team and they need to be sorted compared to their other team members. Currently they’re being sorted globally. So, if Bob, Tom and Arnold belong to team 1 I would only rank them against each other. And if Ann and Ted belonged to team 2, I would NOT want to rank them against Bob, Tom and Arnold.

I changed part of ScallioXTX’s script to the following, but I’m not sure this is the right way to go…

Sorry about the syntax below… the forum wouldn’t format it correctly using php

foreach($data[‘info’] as $team) {
$teams = $team->team_id;
}
$teams = array_unique($teams);

foreach($teams as $team) {
foreach($data[‘info’] as $cache) {
$values = unserialize($cache->cache_data);
$user_rankings[$values[‘user_id’]] .= $values[‘percentage’];
}
}

Looks okay to me. Assuming it works, that is :slight_smile:

(I didn’t test it)

If I understand you right, you want them sorted, but only within the same team correct?

I would store it in an array like this, of course you wouldn’t hardcode the array(‘Ann’ =>100) portion, that would be done when doing the query.

$teams[$team->team_id][‘Ann’] = 100;
$teams[$team->team_id][‘Bob’] = 150;

Then you would do a


foreach ($teams as $id=>$team)
{
    $sorted[$id] = rank_by_values($team); # ScallioXTX's function
}

Hope that makes sense.

Thanks for the help guys.

I racking my rain at this. I’m soooo close, but it’s just not working. This is a simplified version of the array I’m dealing with. I’m having a hard time sorting the users by team and then ranking them. I think at this point I’m just too close and just can’t see past what I’ve already done.

I could figure this out by performing a sql query for each team, but I’d like to try and use the information I already have, which is currently driving me nuts :stuck_out_tongue:

Array
(
    [0] => stdClass Object
        (
            [user_id] => 100
            [team_id] => 1
            [cache] => (user's weight, etc., which is serialized)
        )


    [1] => stdClass Object
        (
            [user_id] => 101
            [team_id] => 1
            [cache] => (user's weight, etc., which is serialized)
        )


    [2] => stdClass Object
        (
            [user_id] => 102
            [team_id] => 2
            [cache] => (user's weight, etc., which is serialized)
        )


    [3] => stdClass Object
        (
            [user_id] => 103
            [team_id] => 2
            [cache] => (user's weight, etc., which is serialized)
        )


)

foreach ($array as $user)
{
    $temp = unserialize($user->cache);
    $teams[$user->team_id][$user->user_id] = $temp['user_weight'];
}


foreach ($teams as $id=>$team)
{
    $sorted[$id] = rank_by_values($team); # ScallioXTX's function
}

print_r($sorted);

Worked perfectly, thank you! :slight_smile:

Going through my testing I found that all users with a weight of 0 (or a negative weight) would tie for first place, so I altered the function, specifically the foreach where it increments the count. It seems to work OK. See any problems with it?

function rank_by_values($array) {
    if(count($array) < 2) {
        return $array;
    }
    
    asort($array);
    $keys = array_keys($array);
    $prev = $array[$keys[0]];
    $i = 1;
    $return = array();
    
    foreach($array as $key => $value) {
        if($value == 0 || substr($value,0,1) == '-') {
            $return[$key] = 'n/a';
        }
        elseif($value != $prev) {
            $i++;
            $return[$key] = $i;
        }
    }
    return $return;
}




Did someone mention an Iterator? :wink:

It’s messy.


<?php
class RankedIterator extends ArrayIterator{


	protected
		$previous,
		$tally,
		$key;


	public function __construct($array, $key){
		$this->key = $key;
		usort($array, array($this, 'sort_by_key'));
		parent::__construct($array);
	}


	public function key()
	{
		$current = parent::current();


		if($this->previous === $current[$this->key]){
			$this->tally++;
		}


		$this->previous = $current[$this->key];


		return (parent::key() - $this->tally) + 1;
	}


	protected function sort_by_key($a, $b){


		$a = $a[$this->key]; $b = $b[$this->key];


		if($a == $b)
		{
			return 0;
		}


		return $a < $b;
	}
}


$users = array(
	array('id' => 235, 'name' => 'Anthony', 'weight' => 344),
	array('id' => 98, 'name' => 'Andrew', 'weight' => 233),
	array('id' => 45, 'name' => 'Alex', 'weight' => 122),
	array('id' => 172, 'name' => 'Remon', 'weight' => 233),
);


foreach(new RankedIterator($users, 'weight') as $rank => $user)
{
	echo $rank, ' - ', $user['name'], PHP_EOL;
}


/*
	1 - Anthony
	2 - Remon
	2 - Andrew
	3 - Alex
*/

Thanks, Anthony. Everything’s working perfectly, but I’m gonna give that a go just so I can learn something new :slight_smile:

@AnthonySterling; Your custom sorter will only ever return 0 or 1 (okay, 0, true or false, coerced this is 0 or 1) so I’m not sure if this will even work.
As a general hint, people tend to think you need to return -1, 0, or 1, but in fact the magnitude doesn’t matter; PHP only cares about the direction (thanks again @Salathe; for that hint!). So the simplest thing to do when writing a custom sorter is to simply return $a - $b; – or indeed the indeed the other way around, depending on whether you want ascending or descending sorting.

:slight_smile:

Thanks ScallioXTX. If @Salathe does find this post, I would imagine that the sort function would be the least of my worries. :slight_smile:

Sound advice, but only useful for integers. Doing the same with floats will give unexpected results where the difference is between -1 and 1, exclusive, since PHP converts the return value of the comparison function to an integer. :eye:

tapes mouth closed :x