PHP - Nested List Broken into Even Columns (fix and updates) - php

I have a follow-up to a previous thread/question that I hope can be solved by relatively small updates to this existing code. In the other thread/question, I pretty much solved a need for a nested unordered list. I needed the nested unordered list to be broken up into columns based on the number of topics.
For example, if a database query resulted in 6 topics and a user specified 2 columns for the layout, each column would have 3 topics (and the related news items below it).
For example, if a database query resulted in 24 topics and a user specified 4 columns for the layout, each column would have 6 topics (and the related news items below it).
The previous question is called PHP - Simple Nested Unordered List (UL) Array.
The provided solution works pretty well, but it doesn't always divide
correctly. For example, when $columns = 4, it only divides the
columns into 3 groups. The code is below.
Another issue that I'd like to solve was brought to my attention by
the gentleman who answered the question. Rather than putting
everything into memory, and then iterating a second time to print it
out, I would like to run two queries: one to find the number of
unique TopicNames and one to find the number of total items in the
list.
One last thing I'd like to solve is to have a duplicate set of
code with an update that breaks the nested unordered list into columns
based on the number of news items (rather than categories). So, this
would probably involve just swapping a few variables for this second
set of code.
So, I was hoping to solve three issues:
1.) Fix the division problem when relying on the number of categories (unordered list broken up into columns based on number of topics)
2.) Reshape the PHP code to run two queries: one to find the number of unique TopicNames and one to find the number of total items in the list
3.) Create a duplicate set of PHP code that works to rely on the number of news items rather than the categories (unordered list broken up into columns based on number of news items)
Could anyone provide an update or point me in the right direction? Much appreciated!
$columns = // user specified;
$result = mysql_query("SELECT * FROM News");
$num_articles = 0;
// $dataset will contain array( 'Topic1' => array('News 1', 'News2'), ... )
$dataset = array();
while($row = mysql_fetch_array($result)) {
if (!$row['TopicID']) {
$row['TopicName'] = 'Sort Me';
}
$dataset[$row['TopicName']][] = $row['NewsID'];
$num_articles++;
}
$num_topics = count($dataset);
// naive topics to column allocation
$topics_per_column = ceil($num_topics / $columns);
$i = 0; // keeps track of number of topics printed
$c = 1; // keeps track of columns printed
foreach($dataset as $topic => $items){
if($i % $topics_per_columnn == 0){
if($i > 0){
echo '</ul></div>';
}
echo '<div class="Columns' . $columns . 'Group' . $c . '"><ul>';
$c++;
}
echo '<li>' . $topic . '</li>';
// this lists the articles under this topic
echo '<ul>';
foreach($items as $article){
echo '<li>' . $article . '</li>';
}
echo '</ul>';
$i++;
}
if($i > 0){
// saw at least one topic, need to close the list.
echo '</ul></div>';
}
UPDATE 12/19/2011: Separating Data Handling from Output Logic (for the "The X topics per column variant"):
Hi Hakre: I've sketched out the structure of my output, but am struggling with weaving the two new functions with the old data handling. Should the code below work?
/* Data Handling */
$columns = // user specified;
$result = mysql_query("SELECT * FROM News LEFT JOIN Topics on Topics.TopicID = New.FK_TopicID WHERE News.FK_UserID = $_SESSION[user_id] ORDER BY TopicSort, TopicName ASC, TopicSort, NewsTitle");
$num_articles = 0;
// $dataset will contain array( 'Topic1' => array('News 1', 'News2'), ... )
$dataset = array();
while($row = mysql_fetch_array($result)) {
if (!$row['TopicID']) {
$row['TopicName'] = 'Sort Me';
}
$dataset[$row['TopicName']][] = $row['NewsID'];
$num_articles++;
}
/* Output Logic */
function render_list($title, array $entries)
{
echo '<ul><li>', $title, '<ul>';
foreach($entries as $entry)
{
echo '<li>', $entry['NewsID'], '</li>';
}
echo '</ul></li></ul>;
}
function render_column(array $topics)
{
echo '<div class="column">';
foreach($topics as $topic)
{
render_list($topic['title'], $topic['entries']);
}
echo '</div>';
}

You have not shown in your both questions what the database table is, so I can not specifically answer it, but will outline my suggestion.
You can make use of aggregation functions in mysql to obtain your news entries ordered and grouped by topics incl. their count. You can do two queries to obtain counts first, that depends a bit how you'd like to deal with your data.
In any case, using the mysql_... functions, all data you selected from the database will be in memory (even twice due to internals). So having another array as in your previous question should not hurt much thanks to copy on write optimization in PHP. Only a small overhead effectively.
Next to that before you take care of the actual output, you should get your data in order so that you don't need to mix data handling and output logic. Mixing does make things more complicated hence harder to solve. For example if you put your output into simple functions, this gets more easy:
function render_list($title, array $entries)
{
echo '<ul><li>', $title, '<ul>';
foreach($entries as $entry)
{
echo '<li>', $entry['NewsID'], '</li>';
}
echo '</ul></li></ul>;
}
function render_column(array $topics)
{
echo '<div class="column">';
foreach($topics as $topic)
{
render_list($topic['title'], $topic['entries']);
}
echo '</div>';
}
This already solves your output problem, so we don't need to care about it any longer. We just need to care about what to feed into these functions as parameters.
The X topics per column variant:
With this variant the data should be an array with one topic per value, like you did with the previous question. I would say it's already solved. Don't know which concrete problem you have with the number of columns, the calculation looks good, so I skip that until you provide concrete information about it. "Does not work" does not qualify.
The X news items per column variant:
This is more interesting. An easy move here is to continue the previous topic with the next column by adding the topic title again. Something like:
Topic A Topic A Topic B
- A-1 - A-5 - B-4
- A-2 Topic B - B-5
- A-3 - B-1 - B-6
- A-4 - B-2
- B-3
To achieve this you need to process your data a bit differently, namely by item (news) count.
Let's say you managed to retrieve the data grouped (and therefore sorted) from your database:
SELECT TopicName, NewsID FROM news GROUP BY 1;
You can then just iterate over all returned rows and create your columns, finally output them (already solved):
$itemsPerColumn = 4;
// get columns
$topics = array();
$items = 0;
$lastTopic = NULL;
foreach ($rows as $row)
{
if ($lastTopic != $row['TopicName'])
{
$topic = array('title' => $row['TopicName']);
$topics[] = &$topic;
}
$topic['entries'][] = $row;
$items++;
if ($items === $itemsPerColumn)
{
$columns[] = $topics;
$topics = array();
$lastTopic = NULL;
}
}
// output
foreach($columns as $column)
{
render_column($column);
}
So this is actually comparable to the previous answer, but this time you don't need to re-arrange the array to obtain the news ordered by their topic because the database query does this already (you could do that for the previous answer as well).
Then again it's the same: Iteration over the returned result-set and bringing the data into a structure that you can output. Input, Processing, Output. It's always the same.
Hope this is helpful.

Related

Count result according level

I have Adjacency list mode structure like that and i want to count all title of parent according level like Food = (2,4,3), Fruit = (3,3)
tree tabel structure
after that make tree like that
by this code i m getting right total like for Food =9, Fruit = 6
function display_children($parent, $level)
{
$result = mysql_query('SELECT title FROM tree '.'WHERE parent="'.$parent.'"');
$count = 0;
while ($row = mysql_fetch_array($result))
{
$data= str_repeat(' ',$level).$row['title']."\n";
echo $data;
$count += 1 + $this->display_children($row['title'], $level+1);
}
return $count;
}
call function
display_children(Food, 0)
Result : 9 // but i want to get result like 2,4,3
But i want to get count total result like that For Food 2,4,3 and For Fruit 3,3 according level
so plz guide how to get total according level
function display_children($parent, $level)
{
$result = mysql_query('SELECT title FROM tree '.'WHERE parent="'.$parent.'"');
$count = "";
while ($row = mysql_fetch_array($result))
{
$data= str_repeat(' ',$level).$row['title']."\n";
echo $data;
if($count!="")
$count .= (1 + $this->display_children($row['title'], $level+1));
else
$count = ", ".(1 + $this->display_children($row['title'], $level+1));
}
return $count;
}
Lets try this once..
If you want to get amounts by level, then make the function return them by level.
function display_children($parent, $level)
{
$result = mysql_query('SELECT title FROM tree WHERE parent="'.$parent.'"');
$count = array(0=>0);
while ($row = mysql_fetch_array($result))
{
$data= str_repeat(' ',$level).$row['title']."\n";
echo $data;
$count[0]++;
$children= $this->display_children($row['title'], $level+1);
$index=1;
foreach ($children as $child)
{
if ($child==0)
continue;
if (isset($count[$index]))
$count[$index] += $child;
else
$count[$index] = $child;
$index++;
}
}
return $count;
}
Note that its hard for me to debug the code as i dont have your table. If there is any error let me know and i will fix it.
Anyways result will be array
which should contain amounts of levels specified by indices:
$result=display_children("Food", 0) ;
var_export($result);//For exact info on all levels
echo $result[0];//First level, will output 2
echo $result[1];//Second level, will output 4
echo $result[2];//Third level, will output 3
And by the way there is typo in your database, id 10 (Beef) should have parent "Meat" instead of "Beat" i guess.
If you want to see testing page, its here.
This article has all you need to creates a tree with mysql, and how count item by level
If you don't mind changing your schema I have an alternative solution which is much simpler.
You have your date in a table like this...
item id
-------------+------
Food | 1
Fruit | 1.1
Meat | 1.2
Red Fruit | 1.1.1
Green Fruit | 1.1.2
Yellow Fruit | 1.1.3
Pork | 1.2.1
Queries are now much simpler, because they're just simple string manipulations. This works fine on smallish lists, of a few hundred to a few thousand entries - it may not scale brilliantly - I've not tried that.
But to count how many things there are at the 2nd level you can just do a regexp search.
select count(*) from items
where id regexp '^[0-9]+.[0-9]+$'
Third level is just
select count(*) from items
where id regexp '^[0-9]+.[0-9]+.[0-9]+$'
If you just want one sub-branch at level 2
select count(*) from items
where id regexp '^[0-9]+.[0-9]+$'
and id like "1.%"
It has the advantage that you don't need to run as many queries on the database, and as a bonus it's much easier to read the data in the tables and see what's going on.
I have a nagging feeling this might not be considered "good form", but it does work very effectively. I'd be very interested in any critiques of this method, do DB people think this is a good solution? If the table were very large, doing table scans and regexps all the time would get very inefficient - your approach would make better use of the any indexes, which is why I say this probably doesn't scale very well, but given you don't need to run so many queries, it may be a trade off worth taking.
An solution by a php class :
<?php
class LevelDepCount{
private $level_count=array();
/**
* Display all child of an element
* #return int Count of element
*/
public function display_children($parent, $level, $isStarted=true)
{
if($isStarted)
$this->level_count=array(); // Reset for new ask
$result = mysql_query('SELECT title FROM tree '.'WHERE parent="'.$parent.'"');
$count = 0; // For the level in the section
while ($row = mysql_fetch_array($result))
{
$data= str_repeat(' ',$level).$row['title']."\n";
echo $data;
$count += 1 + $this->display_children($row['title'], $level+1,false);
}
if(array_key_exists($level, $this->level_count))
$this->level_count[$level]+=$count;
else
$this->level_count[$level]=$count;
return $count;
}
/** Return the count by level.*/
public function getCountByLevel(){
return $this->level_count;
}
}
$counter=new LevelDepCount();
$counter->display_children("Food",0);
var_dump($counter->getCountByLevel());
?>
If you modify your query you can get all the data in one swoop and without that much calculations (code untested):
/* Get all the data in one swoop and arrange it for easy mangling later */
function populate_data() {
$result = mysql_query('SELECT parent, COUNT(*) AS amount, GROUP_CONCAT(title) AS children FROM tree GROUP BY parent');
$data = array();
while ($row = mysql_fetch_assoc($result)) {
/* Each node has the amount of children and their names */
$data[$row['parent']] = array($row['children'], int($row['amount']));
}
return $data;
}
/* The function that does the whole work */
function get_children_per_level($data, $root) {
$current_children = array($root);
$next_children = array();
$ret = array();
while(!empty($current_children) && !empty($next_children)) {
$count = 0;
foreach ($current_children as $node) {
$count += $data[$node][0]; /* add the amount */
$next_children = array_merge($next_children, explode($data[$node][1])); /* and its children to the queue */
}
ret[] = $count;
$current_children = $next_children;
$next_children = array();
}
return $ret;
}
$data = populate_data();
get_children_per_level($data, 'Food');
It shouldn't be difficult to modify the function to make a call per invocation or one call per level to populate the data structure without bringing the whole table into memory. I'd suggest against that if you have deep trees with just a few children as it is a lot more efficient to get all the data in one swoop and calculate it. If you have shallow trees with a lot of children, then it may be worth changing.
It would also be possible to put everything together in a single function, but I'd avoid re-calculating data for repeated calls when they are not needed. A possible solution for this would be to make this a class, use the populate_data function as the constructor that stores it as an internal private property and a single method that is the same as get_children_per_level without the first parameter as it would get the data off its internal private property.
In any case, I'd also suggest you use the ID column as a "parent" reference instead of other columns. To start with, my code will break if any of the names contains a comma :P. Besides, you may have two different elements with the same name. For example, you could have Vegetables -> Red -> Pepper and the Red will get slumped together with the Fruit's Red.
Another thing to note is that my code will enter an infinite loop if your DB data is not a tree. If there is any cycle in the graph, it will never finish. That bug could be easily solved by keeping a $visited array with all the nodes that have already been visited and not pushing them into the $next_children array within the loop (probably using array_diff($data[$node][1], $visited).

Database data in PHP array

I have a table in phpmyadmin that stores an 'id' (auto inc), 'title' and a 'date'.
I have a webpage where I display the 10 latest items (I simply order by ID).
On that page I print the title and the date. My wish is to also display the number of the posted item, so the first posted item is 1, the second is 2, etc. I cannot simply print the ID from the database because if I delete a row, the numbers aren't straight anymore.
My idea was to put all the data in an array but I have no clue what the best way to do this is and how I could print that item number. So for example when I want to display the 54th item I can easily print $items[54][id] or something and it will show me the number 54 and to display the title I print $items[54][title].
I don't know if there are simpler methods, plus arrays always start at 0, but my items must start at 1.
Besides this page that shows the 10 latest items, there is another page where it gets the title of the item out of the URL. How will I be able to search the title in the array and display the data the same way but only for that requested title?
Thanks in advance!
"SELECT COUNT(id) as cnt FROM mytable";
you can select the count of all database entries.
and then assign it to your iterator
$i = $row['cnt']; // this will hold the ammount of records e.g. 21
// other query
while($row = mysql_fetch_assoc($result)) {
echo $i;
$i--; // this will decrement on every iteration 21, 20 , 19, and so on.
}
First off. I would add a timestamp field to the database and order by that instead as it feels overall more reliable and gives you additional details which may prove handy later.
To create the multidimensional array I would do something like:
$result = mysql_query(...);
$items = array();
while($item = mysql_fetch_assoc($result)) {
$items[] = $item;
}
Now $items[12] for example would give you item number 13 (since it's 0-indexed).
Lastly, to select only the item with a specific title I would use a query which included a WHERE clause, like
"SELECT ... FROM ... WHERE title = '".$title."'"
It's very important to sanitize this variable before using it in the query though.
You can read more about MySQL on a lot of places. A quick googling gave me this: http://www.tutorialspoint.com/mysql/index.htm
You should learn PHP before starting to program in PHP ;) Read and work through the PHP manual and some tutorials!
As to your question it is a simple loop you want to do. One way of doing it as an example.
Fetch the 10 last items from the database in any way you like, following some code, partly pseudo-code.
$markup = '';
for ($i=1; $i<=count($items); $i++)
{
$markup .= 'Item ' . $i . ': ' . $items['date'] . ' - ' . $items['title'];
$markup .= 'read more';
$markup .= PHP_EOL;
}
echo $markup;
I don't know how you print out your data exactly, but I assume there is a loop in there. Simply set a counter that increments by one at every row and print its value.
As for the title search, you'll have to run another query with a WHERE title = '$title' condition, but beware of SQL injection.

Creating a tiered commenting system (efficiently with PHP and MySQL [1 table])

I would like to have a comment section with replies to comments. The replies will only go one level. For example.
Parent Comment
-- Here is a reply
-- Here is another reply
-- It won't go further than this one tier
My MySQL looks like this:
comment_id, comment, parents_id
if parents_id is 0, it is the parent. if it has a number, that number will correspond to the comment_id, as it will be its child.
now, i've done this crappy code below, but it seems the second loop messes it up and only displays the first div correctly with its children. i believe it is because i'm calling mysql_fetch_row twice...
$query_show_comments = "SELECT * FROM article_comments WHERE article_id = '$article_id'";
$results_show_comments = mysql_query($query_show_comments);
$num_rows_comments = mysql_num_rows($results_show_comments);
for ($i = 0; $i < $num_rows_comments; $i++) {
$comment = mysql_fetch_row($results_show_comments);
echo "<p>comment_id: $comment[0]</p>";
if ($comment[5] == 0) {
echo <<<_HTML
<div class="dispArticle">
<p><strong>Commenter Name commented # 11/22/10 10:10:10pm</strong></p>
<p>$comment[2]</p>
_HTML;
for ($j = 0; $j < $num_rows_comments; $j++) {
$replies = mysql_fetch_row($results_show_comments);
if ($replies[5] > 0 AND $replies[5] == $comment[0]) {
echo <<<_HTML
<div class="comment"><p><strong>Reply Name replied # 11/22/10 10:10:10pm</strong></p>
<p>child_id: $replies[0]</p>
<p>parent_id: $comment[0]</p>
<p>$replies[2]</p>
</div>
<br />
_HTML;
}
}
}
echo "</div>";
}
Been searching for hours and this is what I've found.
Use multiple tables (would like to keep it in one table so less queries)
Use multiple queries (same as above)
Feed into an array first then sort it all out (what if the comments are long and there are a lot? I just did a query AND had to do more server side processing of feeding it into an array, sorting then displaying...)
The problem is that mysql_fetch_row() will always fetch the next row returned by the query, and that could be in any order. For what you are doing to work, you would need a post to be followed immediately by its child comments every time. This is a shaky solution, so I would suggest you use #3 as it is really the same thing as what you are doing.
I also have a couple of suggestions: use mysql_fetch_assoc() over mysql_fetch_row() and use the names of the columns rather than their numbers as this makes the code much more readable and easier to use. You will have to change your query to order by the ascending parent ID to ensure that all parents are set first. Then:
$query = "query";
$result = mysql_query($query);
$comments = array();
while ($row = mysql_fetch_assoc($result)) {
if ($row['parent_id']) {
$comments[$row['parent_id']]['children'][] = $row;
}
else {
$row['children'] = array();
$comments[$row['comment_id']] = $row;
}
}
Now all of the children are associated with parents. Just iterate through the array.
It's not too hard. You should store all comments in one table and have parent_id
parent_id of 0 means it's a comment, parent_id > 0 would point to id of a message in the same table for which it's a reply.
You would also have article_id, just like in your current example.
The trick you need is to do just one SQL select but reference the same table twice.
You sql will be something like this:
SELECT
M.id as id,
M.id as com_mid,
M.post_subject as com_subject,
M.message_body as com_body,
M2.id as rpl_mid,
M2.post_subject as rpl_subject,
M2.message_body as rpl_body,
M2.parent_id
FROM
MESSAGES AS M
LEFT JOIN MESSAGES as M2 on M2.parent_message_id = M.id
WHERE M.article_id = :aid
AND M.parent_id = 0
ORDER BY com_mid ASC,
rpl_mid ASC
Then once you get result of this sql, you will easily figure out how to handle the result array to display messages and replies
You need a second query. Here's an example TRYING to use your code.
$query_show_comments = "SELECT * FROM article_comments WHERE article_id = '$article_id'";
$results_show_comments = mysql_query($query_show_comments);
$num_rows_comments = mysql_num_rows($results_show_comments);
for ($i = 0; $i < $num_rows_comments; $i++) {
$row_comment = mysql_fetch_row($results_show_comments);
echo "<p>comment_id: $row_comment[0]</p>";
if ($row_comment[5] == 0) {
echo <<<_HTML
<div class="dispArticle">
<p><strong>Commenter Name commented # 11/22/10 10:10:10pm</strong></p>
<p>$row_comment[2]</p>
_HTML;
$query_show_replies = "SELECT * FROM article_comments WHERE parent_id = '$article_id'";
$result_replies = mysql_query($query_show_replies);
while( $row_reply = mysql_fetch_row($results_show_comments) ) )
echo "
<div class=\"comment\"><p><strong>Reply Name replied # 11/22/10 10:10:10pm</strong></p>
<p>child_id: $row_reply[0]</p>
<p>parent_id: $row_reply[1]</p>
</div><br />
";
}
}
echo "</div>";
}
You're doing several things in your code that I don't like to do, not saying it can't be don't that way. My advice is to take a more advanced approach to architecting your web applications:
Use while() loops when reading data from queries, it's more error tolerant
don't use the "echo <<<" blocks because it makes code harder to read
technically speaking, you'll want to use htmlspecialchars on all output to a web page, so the <<< shouldn't be used anyways
better yet, use a template system to extricate your markup (view) from your PHP, conside Smarty because it's easy even if performance isn't quite stellar
in fact, while you're at it, consider abstracting your data code into a separate layer
no matter how you get the data, you shouldn't rely on indexed fields when you're using a SELECT *.. because the order could change. What I mean is, instead of using $comment[0] or $comment[1], use $comment['id'] or htmlspecialchars($comment['text'])

PHP Order in alphabetical order

I'm trying to make a simple alphabetical list to order items in my database. The thing I can't figure out is how to actually list it.
I would like it to be the same format as you have on miniclip.com
Here's an image
I looked around, but couldnt find an answer really.
(I would like it to finish even at the end of each vertical column, except the last one for sure)
Any help would be welcome!
In MySQL:
SELECT * FROM table ORDER BY name ASC
In PHP:
$fruits = array("lemon", "orange", "banana", "apple");
sort($fruits);
foreach ($fruits as $key => $val) {
echo "fruits[" . $key . "] = " . $val . "\n";
}
fruits[0] = apple
fruits[1] = banana
fruits[2] = lemon
fruits[3] = orange
Assuming that your result set already is sorted by using the ORDER BY clause, to group the results by their first character you just need to remember the first character of the previous entry and print out the first character of the current entry if they are different. So:
$prevLabel = null;
while ($row = mysql_fetch_assoc($result)) {
$currLabel = strtoupper(substr($row['name'], 0, 1));
if ($currLabel !== $prevLabel) {
echo $currLabel;
$prevLabel = $currLabel;
}
echo $row['name'];
}
This will print the first character as a label for each group that’s members have the same first character.
He doesn't seem to have an issue with the storting, but doing the column format and headers for each new letter.
Suppose $arr contains your alphabetically sorted list with numeric keys. each element has indexes 'name' and 'link'. This should be pretty safe assumption for data from a SQL query.
$firstLetter = -1;
$desiredColumns = 4; //you can change this!
$columnCount = (count($arr)+27)/$desiredColumns+1;
echo "<table><tr><td>";
foreach($arr as $key => $cur)
{
if ($key != 0 && $key % desiredColumns == 0) echo "</td><td>";
if ($cur['name'][0] !== $firstLetter)
{
echo "<strong>$firstLetter</strong> <br />"; $firstLetter = $cur['name'][0];
}
echo "".$cur['name']."<br />";
}
echo "</td><tr></table>";
You'll have to treat numbers as a special case, but this is the idea. If you are using a template engine there are obviously better ways of doing this, but I figure you would have mentioned that. This is a rough sketch, making pretty HTML isn't my thing.
--Query-- get table into $arr. I can't see your tables obviously, Im making assumptions if names nad stuff so you'll need to verify or change them
$sql = "SELECT * FROM table T ORDER BY name";
$conn = //you should have this
$res = mysql_query($sql, $conn);
$arr = array();
while($row = mysql_fetch_assc($res)
$arr[] = $row;
// start above code here. This isn't safe for empty query responses or other error but it works
I presume you're using MySQL (or another SQL) database, in which case you should simply retrieve the data in the required order using a SORT BY clause on the lookup SELECT. (Sorting this PHP is trivial via the sort function, but it makes sense to get the database to do this - that's pretty much what it's for.)
In terms of balancing the output of each of the columns, you could get a COUNT of the required rows in your database (or simply use the count of the resulting PHP array of data) and use this to ensure that the output is balanced.
As a final thought, if this is going to be output on a per-page basis, I'd highly recommend generating it into a static file when the structure changes and simply including this static file as a part of the output - generating this on the fly is needlessly resource inefficient.
The mysql option mentioned above is definitely the best bet. If the data comes out of the DM in order, that's the simplest way to go.
Your next option might be to look at the
asort and ksort functions in PHP to find the exact one you're looking for.
http://www.php.net/manual/en/array.sorting.php
How are you pulling the data?
<?php
$result = mysql_query("SELECT titles FROM gamelist ORDER BY title ASC");
while ($row = mysql_fetch_assoc($result)) {
echo "{$result['title']}<br/>";
}
?>
There are two ways to do it.
You could use your database and use the 'order' clause to pull them by a specific field alphabetically.
You could also use either a key sort or value sort on a PHP array.
The PHP functions are sort($array) and ksort($array).
http://php.net/manual/en/function.sort.php
http://php.net/manual/en/function.ksort.php
<?php
$list = $your_list_array_from_database
//if you need info on how to do this, just let me know
sort($list);
foreach($list as $item) {
echo $item;
}
?>
I found this post and had the same problem. I used the code below to output a list by category name with a header equal to the first letter. In my database table (category) I have name and category_letter. So, name = football and category_list = 'F'.
<section>
<?php
try {
$cats_sql = $dbo->prepare("SELECT name, category_list, FROM category WHERE category_list REGEXP '^[A-Z#]' GROUP BY category_list ASC");
$cats_sql->execute();
$results_cats = $cats_sql->fetchAll();
} catch(PDOException $e) {
include('basehttp/error');
}
$array_cats = $results_cats;
if(is_array($array_cats)) {
foreach($array_cats as $row_cats) {
$cat_var = $row_cats[category_list]; // Each Category list title
?>
<aside>
<h1><a name=""><? echo $cat_var ?></a></h1>
<?php
try {
$search_sql = $dbo->prepare("SELECT name, category_list FROM category WHERE category_list=:cat_var ORDER BY name ASC"); // Pulling a list of names for the category list
$search_sql->bindParam(":cat_var",$cat_var,PDO::PARAM_STR);
$search_sql->execute();
$results_search = $search_sql->fetchAll();
} catch(PDOException $e) {
include('basehttp/error');
}
$array_search = $results_search;
if(is_array($array_search)) { // Output list of names which match category
foreach($array_search as $row_search) {
?>
<h2><?php echo $row_search[name]; ?></h2>
<br class="clear">
<?php
}
}
?>
</aside>
<br class="clear">
<?php
}
}
?>
</section>
Its actually Simple....I did similar thing for my project once. I had to pull out all music albums name and categorize them in alphabetical order.
In my table, "album_name" is the column where names are stored.
$sql= "select * from album_table order by album_name ASC";
$temp_char= ""; // temporary variable, initially blank;
using while loop, iterate through records;
while($row= $rs->fetch_assoc())
{
$album_name= $row['album_name'];
$first_char_of_albm= $album_name[0]; // this will store first alphabet;
$first_char_of_albm= strtoupper($first_char_of_albm); // make uppercase or lower as per your needs
if($temp_char!=$first_char_of_albm)
{
echo $first_char_of_albm;
$temp_char= $first_char_of_albm; // update $temp_char variable
}
}
That's it....
I am posting my answer to this old question for 3 reasons:
You don't always get to write your queries to MySQL or another DBMS, as with a web service / API. None of the other answers address PHP sorting without query manipulation, while also addressing the vertical alphabetical sort
Sometimes you have to deal with associative arrays, and only a couple other answers deal with assoc. arrays. BTW, my answer will work for both associative and indexed arrays.
I didn't want an overly complex solution.
Actually, the solution I came up with was pretty simple--use multiple tags with style="float:left", inside of a giant table. While I was sceptical that having multiple tbody tags in a single table would pass HTML validation, it in fact did pass without errors.
Some things to note:
$numCols is your desired number of columns.
Since we are floating items, you may need to set the width and min-width of parent elements and/or add some <br style="clear: both" />, based on your situation.
for alternative sorting methods, see http://php.net/manual/en/array.sorting.php
Here's my full answer:
function sortVertically( $data = array() )
{
/* PREPARE data for printing */
ksort( $data ); // Sort array by key.
$numCols = 4; // Desired number of columns
$numCells = is_array($data) ? count($data) : 1 ;
$numRows = ceil($numCells / $numCols);
$extraCells = $numCells % $numCols; // Store num of tbody's with extra cell
$i = 0; // iterator
$cCell = 0; // num of Cells printed
$output = NULL; // initialize
/* START table printing */
$output .= '<div>';
$output .= '<table>';
foreach( $data as $key => $value )
{
if( $i % $numRows === 0 ) // Start a new tbody
{
if( $i !== 0 ) // Close prev tbody
{
$extraCells--;
if ($extraCells === 0 )
{
$numRows--; // No more tbody's with an extra cell
$extraCells--; // Avoid re-reducing numRows
}
$output .= '</tbody>';
}
$output .= '<tbody style="float: left;">';
$i = 0; // Reset iterator to 0
}
$output .= '<tr>';
$output .= '<th>'.$key.'</th>';
$output .= '<td>'.$value.'</td>';
$output .= '</tr>';
$cCell++; // increase cells printed count
if($cCell == $numCells){ // last cell, close tbody
$output .= '</tbody>';
}
$i++;
}
$output .= '</table>';
$output .= '</div>';
return $output;
}
I hope that this code will be useful to you all.

Can this PHP code be simplified to improve performance?

The goal of this code, is to get all brands for all stores into one array, and output this to the screen. If a brand exists in multiple stores, it will only be added once.
But I feel I have too many for loops, and that it might choke the CPU on heavy traffic.
Is there a better solution to this?
function getBrands($stores, $bl)
{
$html = "";
//Loop through all the stores and get the brands
foreach ($stores as $store)
{
//Get all associated brands for store
$result = $bl->getBrandsByStore($store['id']);
//Add all brands to array $brands[]
while ($row = mysql_fetch_array($result))
{
//If this is the first run, we do not need to check if it already exists in array
if(sizeof($brands) == 0)
{
$brands[] = array("id" => $row['id'], "name" => $row['name']);
}
else
{
// Check tosee if brand has already been added.
if(!isValueInArray($brands, $row['id']))
$brands[] = array("id" => $row['id'], "name" => $row['name']);
}
}
}
//Create the HTML output
foreach($brands as $brand)
{
$url = get_bloginfo('url').'/search?brandID='.$brand['id'].'&brand='.urlSanitize($brand['name']);
$html.= ''.$brand['name'].', ';
}
return $html;
}
//Check to see if an ID already exists in the array
function isValueInArray($values, $val2)
{
foreach($values as $val1)
{
if($val1['id'] == $val2)
return true;
}
return false;
}
From your comment, you mention "Guide table has X stores and each store has Y brands". Presumably there's a "stores" table, a "brands" table, and a "linkage" table, that pairs store_id to brand_id, in a one-store-to-many-brands relationship, right?
If so, a single SQL query could do your task:
SELECT b.`id`, b.`name`
FROM `stores` s
LEFT JOIN `linkage` l
ON l.`store`=s.`id`
LEFT JOIN `brands` b
ON b.`id`=l.`brand`
GROUP BY b.`id`;
That final GROUP BY clause will only show each brand once. If you remove it, you could add in the store ID and output the full list of store-to-brand associations.
No need to loop through two sets of arrays (one to build up the array of brands, and then one to make the HTML). Especially since your helper function does a loop through -- use the array_key_exists function and use the ID as a key. Plus you can use the implode function to join the links with ', ' so you don't have to do it manually (in your existing code you'd have a comma on the end you'd have to trim off). You can do this without two sets of for loops:
function getBrands($stores, $bl)
{
$brands = array();
//Loop through all the stores and get the brands
foreach ($stores as $store)
{
//Get all associated brands for store
$result = $bl->getBrandsByStore($store['id']);
//Add all brands to array $brands[]
while ($row = mysql_fetch_array($result))
{
if (!array_key_exists($row['id'])
{
$url = get_bloginfo('url') . '/searchbrandID=' .
$brand['id'] . '&brand=' . urlSanitize($brand['name']);
$brands[$row['id']] .= '<a href="' . $url . '" id="' .
$brand['id'] . '" target="_self">' .
$brand['name'] . '</a>';
}
}
}
return implode(', ', $html);
}
That will get you the same effect a little faster. It's going to be faster because you used to loop through to get the brands, and then loop through and build up the HTML. Don't need to do that as two separate loops so it all at once and just store the HTML as you go along. Plus since it's switched to use array_key_exists, instead of the helper you wrote that checks by looping through yet again to see if a brand is in there, you'll see more speed improvements. Hashmaps are nice like that because each element in the hashmap has a key and there are native functions to see if a key exists.
You could further optimize things by writing a better SQL statement with a distinct filter to make it so you don't have to do a while inside a foreach.
How are your tables designed? If you had a store table, a brand table, and a link table that had the relationship between stores and brands, you could just pull in the list of brands from the brand table in one query and not have to do any other logic.
Design your tables so they easily answer the questions you need to ask.
If you need to get all the brands for a certain set of stores then you should consider using a query crafted to do that instead of iterating through all the stores and getting the separate pieces of information.

Categories