How to properly write this usort function
I have an array of rows from a database I need to sort by two parameters. I have tried to use the usort()
function to accomplish this, but I'm running into some trouble.
Here is my code:
if ($sort == 'rating-desc') usort($records, array('browse_model', 'cmp'));
private function cmp($a, $b) {
$ratingCmp = strcmp($b['rating'], $a['rating']);
if ($ratingCmp == 0) {
return strcmp($b['title'], $a['title']);
} else {
return $ratingCmp;
}
}
Here is the print_r()
result of the array before usort()
:
Array
(
[0] => Array
(
[isbn] => 1847199488
[title] => CodeIgniter 1.7
[rating] => 3.5
)
[1] => Array
(
[isbn] => 059615593X
[title] => CSS Cookbook, 3rd Edition (Animal Guide)
[rating] => 3.5
)
[2] => Array
(
[isbn] => 0321637984
[title] => Essential Facebook Development: Build Successful Applications for the Facebook Platform
[rating] => 3.5
)
[3] => Array
(
[isbn] => 0980576857
[title] => jQuery: Novice to Ninja
[rating] => 4.5
)
[4] => Array
(
[isbn] => 0596157134
[title] => Learning PHP, MySQL, and JavaScript: A Step-By-Step Guide to Creating Dynamic Websites (Animal Guide)
[rating] => 4.5
)
)
And here is the the result after the usort()
(sorted by rating, but not by title):
Array
(
[0] => Array
(
[isbn] => 0980576857
[title] => jQuery: Novice to Ninja
[rating] => 4.5
)
[1] => Array
(
[isbn] => 0596157134
[title] => Learning PHP, MySQL, and JavaScript: A Step-By-Step Guide to Creating Dynamic Websites (Animal Guide)
[rating] => 4.5
)
[2] => Array
(
[isbn] => 0321637984
[title] => Essential Facebook Development: Build Successful Applications for the Facebook Platform
[rating] => 3.5
)
[3] => Array
(
[isbn] => 1847199488
[title] => CodeIgniter 1.7
[rating] => 3.5
)
[4] => Array
(
[isbn] => 059615593X
[title] => CSS Cookbook, 3rd Edition (Animal Guide)
[rating] => 3.5
)
)
So it puts them in order by rating, but not by title. How can I modify this so it sorts by rating, then by title?
For what it's worth, I've also tried this:
if ($sort == 'rating-desc') {
foreach ($records as $key => $row) {
$rating[$key] = $row['rating'];
$title[$key] = $row['title'];
}
array_multisort($title, SORT_DESC, $rating, SORT_ASC, $records);
}
But that result isn't correct either. It shows the same result as the usort I tried above.
What am I missing?
Thanks much,
MarcusEDIT: Following some of the suggestions below, here are several things I've tried. None of them solve my problem. They all return the ratings sorted properly, but the title is descending.
private function cmp($a, $b) {
if ($a['rating'] == $b['rating']) {
return strcasecmp($b['title'], $a['title']);
}
return $b['rating'] - $a['rating'];
}
private function cmp($a, $b) {
if ($a['rating'] == $b['rating'])
return strcasecmp($a['title'], $b['title']);
return $b['rating'] - $a['rating'];
}
if ($sort == 'rating-desc') {
foreach ($records as $key => $row) {
$rating[$key] = $row['rating'];
$title[$key] = $row['title'];
}
array_multisort($rating, SORT_DESC, $title, SORT_ASC, $records);
}
if ($sort == 'rating-desc') {
foreach ($records as $key => $row) {
$rating[$key] = $row['rating'];
$title[$key] = $row['title'];
}
array_multisort($rating, SORT_DESC, $title, SORT_DESC, $records);
}
Here is my entire code example - the entire model...
class Browse_model extends Model {
function Browse_model() {
parent::Model();
}
function get_form_tags() {
$sql = 'select t.id, t.tag, coalesce(btc.count, 0) as count from tags as t left outer join (select tag_id, count(*) as count from books_tags group by tag_id) as btc on t.id = btc.tag_id order by count desc, tag asc';
$query = $this->db->query($sql);
$tags = $query->result();
return $tags;
}
function get_book_info($tags, $andor, $read, $sort) {
/*
* SELECT b.isbn, b.title, b.publisher, b.date, b.thumb, b.filename, b.pages, t.tag
* FROM books AS b
* INNER JOIN books_tags AS bt ON b.isbn = bt.book_id
* INNER JOIN tags AS t ON bt.tag_id = t.id
* ORDER BY b.title, t.tag
*/
switch ($sort) {
case 'alpha-desc':
$order = 'b.title DESC, t.tag';
break;
case 'date-desc':
$order = 'b.date DESC, b.title, t.tag';
break;
case 'date-asc':
$order = 'b.date, b.title, t.tag';
break;
default:
$order = 'b.title, t.tag';
break;
}
$this->db->select('b.isbn, b.title, b.publisher, b.date, b.thumb, b.filename, b.pages, t.tag');
$this->db->from('books AS b');
$this->db->join('books_tags AS bt', 'b.isbn = bt.book_id', 'inner');
$this->db->join('tags AS t', 'bt.tag_id = t.id', 'inner');
$this->db->order_by($order);
$query = $this->db->get();
$result = $query->result();
$counter = '';
$records = $meta = $tags = array();
$count = count($result);
$i = 1;
foreach ($result as $book) {
// If this is not the last row
if ($i < $count) {
// If this is the first appearance of this book
if ($counter != $book->isbn) {
// If the meta array already exists
if ($meta) {
// Add the combined tag string to the meta array
$meta['tags'] = implode(', ', $tags);
// Add the meta array
$records[] = $meta;
// Empty the tags array
$tags = array();
}
// Reset the counter
$counter = $book->isbn;
// Grab the book from Amazon
$amazon = $this->amazon->get_amazon_item($book->isbn);
// Collect the book information
$meta = array(
'isbn' => $book->isbn,
'title' => strip_slashes($book->title),
'publisher' => strip_slashes($book->publisher),
'date' => date('F j, Y', strtotime($book->date)),
'thumb' => $book->thumb,
'file' => $book->filename,
'pages' => $book->pages,
'rating' => $amazon->Items->Item->CustomerReviews->AverageRating,
'raters' => $amazon->Items->Item->CustomerReviews->TotalReviews
);
// Add the tag to the tags array
$tags[] = $book->tag;
} else {
// All we need is the tag
$tags[] = $book->tag;
}
// If this is the last row
} else {
// If this is the first appearance of this book
开发者_开发问答 if ($counter != $book->isbn) {
// Grab the book from Amazon
$amazon = $this->amazon->get_amazon_item($book->isbn);
// Collect the book information
$meta = array(
'isbn' => $book->isbn,
'title' => strip_slashes($book->title),
'publisher' => strip_slashes($book->publisher),
'date' => date('F j, Y', strtotime($book->date)),
'thumb' => $book->thumb,
'file' => $book->filename,
'pages' => $book->pages,
'rating' => $amazon->Items->Item->CustomerReviews->AverageRating,
'raters' => $amazon->Items->Item->CustomerReviews->TotalReviews
);
}
// All we need is the tag
$tags[] = $book->tag;
// Add the combined tag string to the meta array
$meta['tags'] = implode(', ', $tags);
// Add the meta array
$records[] = $meta;
}
$i++;
}
echo '<code><pre>';
print_r($records);
echo '</pre></code>';
if ($sort == 'rating-desc') usort($records, array('browse_model', 'cmp'));
echo '<code><pre>';
print_r($records);
echo '</pre></code>';
return $records;
}
private function cmp($a, $b) {
if ($a['rating'] == $b['rating'])
return strcasecmp($b['title'], $a['title']);
return $b['rating'] - $a['rating'];
}
}
Your array is sorted, both by rating and by title. The problem is that you're sorting case-sensitively, and 'a' comes after 'Z'.
Try using strcasecmp
instead of strcmp
when comparing titles.
Also, if the rating will always be a number, you should probably be comparing ratings numerically rather than stringwise. Use $b['rating'] - $a['rating']
instead of strcmp
.
EDIT:
I took the same function you had originally, and the same data, and whipped up a test script. With the function in a class and marked private
, PHP warned that the callback to usort
was invalid, but continued to run (not touching the array). With the function marked public
, though, it works as expected. This leads me to believe that either PHP hates your callback being to a private function, or the class name is wrong.
If you want to be able to use a private function, the call to usort
apparently needs to be in a function in the same class. Otherwise usort
complains about the callback being invalid and refuses to run.
class Stuff
{
public function sort(&$arr)
{
usort($arr, array('Stuff', 'cmp'));
}
private function cmp($a, $b)
{
$ratingCmp = $b['rating'] - $a['rating'];
if ($ratingCmp == 0) {
return strcasecmp($a['title'], $b['title']);
} else {
return $ratingCmp;
}
}
}
Stuff::sort($records);
print_r($records);
For me, this prints out the books sorted descending by rating, then ascending by title. If this doesn't work for you, something's funky.
Use simple boolean comparison operators for comparing numeric fields such as rating, rather than strcmp
:
private function cmp($a, $b) {
if ($b['rating'] == $a['rating'])
return strcmp($b['title'], $a['title']);
return $b['rating'] - $a['rating'];
}
It also looks like you're sorting descending by both rating and title; change your strcmp
to return strcmp($a['title'], $b['title'])
to sort by rating(descending) and title(ascending).
You could alternatively use array_multisort()
deleted code
Creates a couple extra arrays, but gets the job done; if you put the code into a function or a method as you're doing (pass $records by reference) those will get destroyed on function exit.
Edit: I see you've put into your question that you've tried this. How doesn't it work?
Edit: This works for me:
foreach ($records as $rec) {
$rating[] = $rec['rating'];
$title[] = strtolower($rec['title']);
}
array_multisort($title, SORT_DESC, $rating, SORT_ASC, $records);
print_r($records);
Another edit:
Given this array:
$records = array(
0 =>
array (
'isbn' => '1847199488',
'title' => 'CodeIgniter 1.7',
'rating' => 3.5,
),
1 =>
array (
'isbn' => '1847199488',
'title' => 'CodeIgniter 1.7',
'rating' => 3,
),
2 =>
array (
'isbn' => '1847199488',
'title' => 'CodeIgniter 1.7',
'rating' => 3.2,
),
3 =>
array (
'isbn' => '1847199488',
'title' => 'CodeIgniter 1.7',
'rating' => 4.5,
),
4 =>
array (
'isbn' => '059615593X',
'title' => 'CSS Cookbook, 3rd Edition (Animal Guide)',
'rating' => 3.5,
),
5 =>
array (
'isbn' => '0321637984',
'title' => 'Essential Facebook Development: Build Successful Applications for the Facebook Platform',
'rating' => 3.5,
),
6 =>
array (
'isbn' => '0980576857',
'title' => 'jQuery: Novice to Ninja',
'rating' => 4.5,
),
7 =>
array (
'isbn' => '0596157134',
'title' => 'Learning PHP, MySQL, and JavaScript: A Step-By-Step Guide to Creating Dynamic Websites (Animal Guide)',
'rating' => 4.5,
),
);
This code:
foreach ($records as $rec) {
$rating[] = $rec['rating'];
$title[] = strtolower($rec['title']);
}
array_multisort($rating, SORT_DESC, SORT_NUMERIC, $title, SORT_ASC, SORT_STRING, $records);
var_export($records);
Will produce this:
array (
0 =>
array (
'isbn' => '1847199488',
'title' => 'CodeIgniter 1.7',
'rating' => 4.5,
),
1 =>
array (
'isbn' => '0980576857',
'title' => 'jQuery: Novice to Ninja',
'rating' => 4.5,
),
2 =>
array (
'isbn' => '0596157134',
'title' => 'Learning PHP, MySQL, and JavaScript: A Step-By-Step Guide to Creating Dynamic Websites (Animal Guide)',
'rating' => 4.5,
),
3 =>
array (
'isbn' => '1847199488',
'title' => 'CodeIgniter 1.7',
'rating' => 3.5,
),
4 =>
array (
'isbn' => '059615593X',
'title' => 'CSS Cookbook, 3rd Edition (Animal Guide)',
'rating' => 3.5,
),
5 =>
array (
'isbn' => '0321637984',
'title' => 'Essential Facebook Development: Build Successful Applications for the Facebook Platform',
'rating' => 3.5,
),
6 =>
array (
'isbn' => '1847199488',
'title' => 'CodeIgniter 1.7',
'rating' => 3.2,
),
7 =>
array (
'isbn' => '1847199488',
'title' => 'CodeIgniter 1.7',
'rating' => 3,
),
)
精彩评论