I'm trying to add a new variable (argument) `$sugg_only = false' to the get_lists method. Right now I'm pulling all my data based on if the user has a sign in token. This works fine and returns the array of data on their custom list.
What I'm trying to do is return stuff from their shopping_list_name if the Suggested column is set to Y. This way if they don't have a custom list it will pull a suggested list that we provide.
Here’s the full list of what’s in the table:
ID SHOPPING_LIST_NAME S SEQUENCE
1 test amnaik shopping list N
2 bonner shopping list N
3 793d7384fa4fa247d6fae07db104147d0a2dad6e Y
4 kj's shopping list N
5 kj's shopping list from 1384201636 N
6 kj's shopping list from 1384201659 N
7 kj's shopping list from 1384202055 N
8 kj's shopping list from 1384202089 N
9 kj's shopping list from 1385064064 N
10 kj's shopping list from 1385064145 N
11 kj's shopping list from 1385064150 N
12 kj's shopping list from 1385064257 N
13 kj's shopping list from 1385064825 N
14 kj's shopping list from 1385064857 N
So, as you see, there’s just one (terribly named) shopping list that’s setup as a suggestion.
// Get a user's shopping lists
public function get_lists($clobber = false, $sugg_only = false) {
if ($this->UserShoppingList != null && !$clobber) {
return $this->UserShoppingList;
} else if ($this->get_sign_in_token()) {
global $db;
$vars = array();
$vars[] = array(':i_sign_in_token', strtoupper($this->get_sign_in_token()));
$rows = $db->get_function_ref_cursor('custom.japi_shopping_list.get_lists_for_shopper(:i_sign_in_token)', $vars);
// Turn the rows into objects and get their items.
foreach ($rows as $row) {
$list = new UserShoppingList(null, $this->sign_in_token);
$list->get_from_array($row);
$list->get_items();
$this->UserShoppingList[] = $list;
}
return $this->UserShoppingList;
} else {
return false;
}
}
api page:
if(!isset($_GET['token']) && !isset($_GET['suggested_only'])) {
die('Must pass-in either a \'token\' or \'suggested_only\' flag');
}
if(isset($_GET['token'])) {
$shopper = new Shopper($_GET['token'])
or die('Could not instantiate a new Shopper from the \'token\' passed-in');
$array = array();
$shopper_lists = $shopper->get_lists(true);
foreach ($shopper_lists as $list) {
$array[] = $list->json();
}
echo json_encode($array);
// echo json_encode($shopper_lists);
}
Will i likely be writing another foreach loop and just including whatever happens if $sugg_only is equal to TRUE??
add something like so to the bottom of my api page??:
if(isset($_GET['suggested_only']) && $_GET['suggested_only'] == 'true')
{
}
Any help would be greatly appreciated. Thank you.
When dealing with arrays I'll usually favour array_key_exists('suggested_only', $_GET) over isset($_GET['suggested_only'])
If there's nothing really different with the execution of the data, just a difference in the data you are selecting, I'd put your statement at the TOP of the API page, not the bottom.
That way, your IF statement can define a flag for when you inevitably call $shopper->get_lists()
You could do something like this:
$suggestions = false;
$clobber = false;
if(isset($_GET['suggested_only']) && $_GET['suggested_only'] == 'true')
{
$suggestions = true;
}
...
$shopper_lists = $shopper->get_lists($clobber, $suggestions);
This way, you don't really duplicate code fetching the data you want.
HOWEVER
When it comes down to it, what if you add a whole series of types of lists to get. You don't want to add a flag and another variable to your get_lists function. It would become a mess.
It would be better to have a single variable like $_GET['type'], and switch it as follows:
switch($_GET['type'])
{
case 'thingo':
$shopper->get_lists('thingo');
//Or you could use $shopper->get_thingo_list();
break;
case 'suggestion':
$shopper->get_lists('suggestion');
//Or you could use $shopper->get_suggestion_list();
break;
case 'list':
$shopper->get_lists('list');
//Or you could use $shopper->get_list();
break;
default:
//No type exists here
break;
}
Some people might suggest to just pass the $_GET variable straight to your function, but I always get a bit nervous doing that for security reasons.
Related
I have a JSON multi-dimensional array of products from Shopify that I am looping through. Each product has 3 options and each option can have infinite numbers of values.
For example:
Lamp (product)
Glass Finish (option)
Clear (value)
Smoke (value)
Metal Finish (option)
Polished Chrome (value)
Drop (option)
600mm (value)
800mm (value)
Within the loop I am creating rows within a 'repeater' field in a CMS (one for each option and then a repeater within each option for each value).
This is all fine BUT I want to do a few checks as the array of products is cached to every 30 minutes so if the cache was updated and either an option was removed or a value to that option was added then it should update.
I have my first check in place (loop through all the existing repeater options that have been added, before doing anything, and if an option already exists then skip it (and thus not creating multiple of the same options).
What I need to do is to check each of the values to see if any new ones have been added. I can write the code to actually add the value to the repeater field but I am unsure how to check as by this point, if the option already exists, it skips over.
Any thoughts?
foreach($product['options'] as $option) {
foreach($p->shop_product_options as $options) {
// If this option already exists... then skip the parent loop on this product
if ($options->global_text == $option['name']) {
continue 2;
}
}
$options = $p->shop_product_options->getNew();
$options->of(false);
$options->global_text = $option['name'];
$options->save();
$p->shop_product_options->add($options);
foreach($option['values'] as $o) {
$values = $options->shop_product_options_option->getNew();
$values->of(false);
$values->global_text = $o;
$values->save();
$options->save();
$options->shop_product_options_option->add($values);
}
$options->save();
}
Still not sure I am completely understanding what you are after but it sounds like you need something like this :
foreach($product['options'] as $option) {
$optionNames = array_column($options, 'name');
$options = $p->shop_product_options->getNew();
foreach($option['values'] as $o) {
$values = $options->shop_product_options_option->getNew();
if($values->global_text === $o && in_array($option->global_text, $optionNames, true)) {
continue 2;
}
$values->of(false);
$values->global_text = $o;
$values->save();
$options->shop_product_options_option->add($values);
}
$options->of(false);
$options->global_text = $option['name'];
$p->shop_product_options->add($options);
$options->save();
}
array_column will create an array getting all the values of a multidimensional array when the key is name, and then the if statement will look for a combination of name and value.
And since the saving logic for the option is below the loop for the values,
it will check the cache before saving anything, this might not be a copy/paste answer, i'm just giving you some logic to work with
I can't think my way through this one. I'm still learning arrays so go easy on me. I'm using codeigniter.
I have 3 tabs (1 month, 2 month, 3 month) in my mark-up.
Each tab shows 3 price boxes (3 levels - basic=1, standard=2, featured=3).
I need to display 9 prices overall, pulled from 1 look-up:
return $this->db->get('prices')->result_array();
In the database it's like this
Should I be trying to do it from one look-up as shown in my model or should I be doing several look-ups, or should I just be managing that look-up in the controller setting vars, ready to display in the view or just doing everything in the view? And How? The only think of 3x foreach loops, where inside the loop I say:
if($prices['months']==3) echo $prices['level'].' is '.$prices['amount'].'<br>';
I'd like to know the BEST way to do this but also how to do the array from one look-up, because I think I really need to get my head around arrays properly. Thanks :)
-- EDIT to show what I've ended up using below --
In the controller, sort of inspired by array_chunk but more manual and to allow for the table to expand, is setting array keys which I read up on in php manual:
foreach ($prices as $price_row) {
$data['prices'][$price_row['months']][] = $price_row;
}
Then in the view I can just use foreach for a month:
foreach ($prices[1] as $p) {
echo level_name($p['level']).' = '.$p['amount'].'<br>';
}
i did not test this so might have made a stupid error - but basically you can foreach through each of your products - make an array - and then use that array in your view.
// in your model Note I am returning an object not an array
// and always check to make sure something got returned
if( ! $products = $this->db->get('prices')->result() )
{
return false:
}
else
{
$prices = array();
foreach($products as $product)
{
// append the months number to the word 'months' to make it clear
$month = $product->months . 'month' ;
// same with level
$level = 'level' . $product->level ;
// build the array
$prices[$month][$level] = $product->amount ;
}//foreach
return $prices ;
}//else
so then in your controller - make sure something came back from the model, assign it to data, then pass data to your view
if( ! $data['prices'] = $this->somemodelname->returnPrices() )
{
$this->showError() ;
}
else
{
$this->load->view('yourviewname', $data);
}
and then in your view you could foreach or just echo out each price if it needs to follow some layout.
echo '1 month level 1 $' . $prices['1month']['level1'] ;
and remember your best friend when doing arrays is print_r wrapped in pre tags so like
echo 'start prices <br> <pre>' ;
print_r($prices) ;
echo '</pre>' ;
opinions - its fine to build stuff in the controller and the view while you are developing and building out. but get in the habit of refactoring to your models. keep your controllers as clean and thin as possible. if your views need complicated data structures - build them in a model first. that way if something goes wrong - your controller can decide what to do. AND you don't have to check in your view if $prices is set and valid because you have already done it. this minimizes where things can go wrong.
Here's a pretty easy way to sort the db return into separate arrays and then display them. #caralot stole my thunder so I came up with this alternative.
Using your current model return $this->db->get('prices')->result_array(); and assigning it to $data.
$data = $this->db->functionName();
//You should check $data validity but I'm skipping that
$month1 = [];
$month2 = [];
$month3 = [];
foreach($data as $row)
{
if($row['months'] === '1')
{
$month1[] = $row;
}
elseif($row['months'] === '2')
{
$month2[] = $row;
}
else
{
$month3[] = $row;
}
}
echo "Month 1<br>";
foreach($month1 as $month){
echo "Level ". $month['level'].' is '.$month['amount'].'<br>';
}
echo "Month 2<br>";
foreach($month2 as $month){
echo "Level ".$month['level'].' is '.$month['amount'].'<br>';
}
echo "Month 3<br>";
foreach($month3 as $month){
echo "Level ".$month['level'].' is '.$month['amount'].'<br>';
}
If your table was less ordered than what you show it would be necessary to add a $this->db->order_by('level', 'ASC'); call to the query.
It is such a tiny dataset that you should definitely do a single lookup. Sorting the array into three arrays in your controller would make more sense as it will leave your view much cleaner, and allow you to set defaults should there be no data (say level 1 for 3 months is removed) if you need to. You are always going to need three foreach loops unless you construct the entire table in one go, setting breaks and new column headings when the level indicator changes.
There is no 'Best' way to do this, it is all case dependent on complexity of layout, future data development and your requirements. Usually though you minimize the number of queries, and keep data manipulation to a minimum within you views. So ideally you will have three arrays, one for each column, sent to your view.
In your controller,
$result = $this->db->query("SELECT * from prices");
$result=$result->result();
$tab1=array();
$tab2=array();
$tab3=array();
foreach ($result as $res) {
switch($res->months)
{
case 1: array_push($tab1, $res);
break;
case 2: array_push($tab2, $res);
break;
case 3: array_push($tab3, $res);
break;
}
}
//var_dump($tab3); //array tab1 for month1, array tab2 for month2, array tab3 for month3
$data['tab1']=$tab1;
$data['tab2']=$tab2;
$data['tab3']=$tab3;
$data['include']=$this->load->view('myview', $data);
In your view i.e myview in my case,
<?php
if(!empty($tab1))
{
echo "Tab 1"."<br>";
foreach($tab1 as $tb)
{
echo "For level ".$tb->level." price is ".$tb->amount."<br>";
}
}
if(!empty($tab2))
{
echo "Tab 2"."<br>";
foreach($tab2 as $tb)
{
echo "For level ".$tb->level." price is ".$tb->amount."<br>";
}
}
if(!empty($tab3))
{
echo "Tab 3"."<br>";
foreach($tab3 as $tb)
{
echo "For level ".$tb->level." price is ".$tb->amount."<br>";
}
}
?>
i'm making a grocerylist system that takes into account amount of recipies you throw at it.
based on that, it combines values and outputs a grocerylist for you.
i'm looping a mysql query and create arrays like this:
foreach sql loop{
$ingredients = array(
"id"=>array($id),
"name"=>array($name),
"amount"=>array($amount),
"unit"=>array($unit)
);
foreach ($ingredienser as $key => $value) {
foreach ($value as $index => $v) {
echo $key."=";
echo $v;
echo "<br><hr>";
}
}
}
That whole ordeal outputs a list like this if i throw 1 recipe at it:
id=150
name=Cherrytomater
amount=300
unit=Gram
id=151
name=Kyllingfilet
amount=4
unit=Stykk
if i throw 2 recipies it throws the next recipe under it.
However, i need it to do a certain set of things.
merge values if the name is similar
ignore the above merge if unit is different (its a shopping list after all)
output a combined list of ingredients based on the recipies and amount of recipies i throw at it.
To further explain; the sql equivalent of what im trying to achieve is :
$dupe = "2" //amount of similar recipies
SELECT i.id, i.ingred, r.enhet, r.id, SUM(r.mengde * $dupe) total, r.enhet FROM oppskriftingred r left join ingrediens i on i.id = r.ingrediens WHERE r.oppskriftid IN $vars2 GROUP BY i.ingred, r.enhet ORDER BY i.ingred
However, the sql approach wont work as the SUM doesn't differentiate between different recipies.. if $ dupe is 4 for one recipe and 2 for another, its 4 for both.
So how can i do this in php with arrays? Any help is greatly appreciated!
Your "this is what I need list" has one problematic point:
1. merge value if the name is SIMILAR
The rest is quite simple. You have an array of ingerdients $ingredients. You need to loop through this and compare every entry to a result array, to figure out if your points 2 and 1 match it to a existing or new entry. I would give the new array a key, that makes matching easy. Let's say the unit and the name as a string: 'Gram-Cherrytomater'
$result = array();
foreach ($ingerdients as $item) {
$newKey = $item['unit'].'-'.$item['name'];
if (isset($result[$newKey]) {
// add amount to existing item
$result[$newKey]['amount'] += $item['amount'];
} else {
// add new item
$result[$newKey] = $item;
}
}
// now loop trough the result and display whatever you want
The problem is with the SIMILAR thing. I do not know, how you want to compare to strings that are similar. You should do this when getting the $newKey. Do something with $item['name'] to make it "similar".
You could use some php function like similar_text(), levenshtein() or soundex(). I do not know how good this works for your language (Danish?).
I have a controller function in CodeIgniter that looks like this:
$perm = $this->job_m->getIdByGroup();
foreach($perm as $pe=>$p)
{
$pId = $p['id'];
$result = $this->job_m->getDatapermission($pId);
}
$data['permission'] = $result;
What I need to do is list the data in the result in the view, but I get only the last value while using this method. How can I pass all the results to the view?
Store it in an array. Like this:
foreach($perm as $pe=>$p){
$result[] = $this->job_m->getDatapermission($p['id']);
}
Because $result is not an array...
try this:
$result=array();
foreach($perm as $pe=>$p)
{
$pId = $p['id'];
$result[] = $this->job_m->getDatapermission($pId);
}
$data['permission'] = $result;
Note:
My answer uses a counter to enable the display of a single group result when needed.
Guessing from your need to loop and display the value of $result, possibly, it is an array or object returned by $query->result(). Things could be a bit complex.
Example: if $perm is an array of 5 items( or groups), the counter assigns keys 1 - 5 instead of 0 - 4 as would [] which could be misleading. Using the first view example, you could choose to display a single group value if you wants by passing it via a url segment. Making the code more flexible and reusable. E.g. You want to show just returns for group 2, in my example, $result[2] would do just that else next code runs. See my comments in the code.
$perm = $this->job_m->getIdByGroup();
$counter = 1;
foreach($perm as $pe=>$p)
{
$pId = $p['id'];
$result[$counter] = $this->job_m->getDatapermission($pId);
$counter++;
}
$data['permission'] = $result;
As mentioned above Note:
I Added a Counter or Key so you target specific level. If the groups are:
Men, Women, Boys, Girls, Children; you'd know women is group two(2) If you desire to display values for just that group, you don't need to rewrite the code below. Just pass the group key would be as easy as telling it by their sequence. To display all the loop without restrictions, use the second view example. To use both, use an if statement for that.
###To access it you could target a specific level like
if(isset($permission)){
foreach($permission[2] as $key => $value){
echo $value->columnname;
}
###To get all results:
foreach($permission as $array){
foreach($array as $key => $value){
echo $value->columnname;
}
}
}
I'm writing a small algorithm in PHP that goes through n number of movies with ratings, and will store the top 5. I'm not reading from a datafile, but from a stream so I cannot simply order the movies by rating.
My question is what is the most efficent way to keep track of the top 5 rated movies as I read the stream? Currently I do the following:
Read in 5 movies (into an array called movies[]), with two keys movies[][name] and movies[][rating]
Order the array by movies[rating] using array_multisort() (highest rating now sits at movies[4])
Read in the next movie
If this new movie rating > movies[0][rating] then replace movies[0] with this new movie
Re-order the list
Repeat 3-5 until finished
My method works, but requires a sort on the list after every read. I believe this to be an expensive method mostly due to the fact that every time I use array_multisort() I must do a for loop on 5 movies just to build the index to sort on. Can anyone suggest a better way to approach this?
Linked lists would work here.
Build a linked list that chains the first 5 movies in the correct order. For each new movie, just start at the the end of the chain and walk it until your movie is between one with a higher rating and one with a lower rating. Then insert your link into the list here. If the movie was better than the worst (and thus your list is now 6 long), just remove the last link in the chain, and you are back to 5.
No sorting, no indexing.
Your algorithm looks fine. I am not sure how the arrays are implemented in PHP. From an algorithm point of view: use a heap instead of an array.
No point in re-sorting after every read since you really only need to insert a new entry. Use the following algorithm, it's likely to get you the best speed. It's basically an unrolled loop, not the most beautiful code.
set movies[0..4].rating to -1.
while more movies in stream:
read in next movie.
if movie.rating < movies[0].rating:
next while
if movie.rating < movies[1].rating:
movies[0] = movie
next while
if movie.rating < movies[2].rating:
movies[0] = movies[1]
movies[1] = movie
next while
if movie.rating < movies[3].rating:
movies[0] = movies[1]
movies[1] = movies[2]
movies[2] = movie
next while
if movie.rating < movies[4].rating:
movies[0] = movies[1]
movies[1] = movies[2]
movies[2] = movies[3]
movies[3] = movie
next while
movies[0] = movies[1]
movies[1] = movies[2]
movies[2] = movies[3]
movies[3] = movies[4]
movies[4] = movie
At the end, you have your sorted list of movies. If there's less than 5, those others will have a rating of -1 so you'll know they're invalid. This is assuming that the rating on a real movie is zero or greater but you can adjust the values if they're not.
If you need to adjust it for more than 5 movies, you can. The best bet would be to roll up the loop again. At some point, however, it's going to become more efficient to sort it than use this method. This method's only really good for a small data set.
My method works, but requires a sort on the list after every read.
No it doesn't, it only requires a sort after you find a new movie whos rating is > movies[0][rating].
This method seems efficient to me. You only sort occasionally when there's a new entry for the top 5, which will happen less the more movies you process.
How big is the list? I'm guessing it's not an option to keep the entire list in memory, and sort it at the end?
there is no need for two keys in array. array with name as key, and rating as value will do. Sort it with arsort();
the algorithm is not perfect, you can do it optimally with linked list. Although I think linked list implemented in PHP will be actually slower that function call to asort() for 6 elements. For big O estimation, you can assume that sorting 6 elements has constant time.
You'll only sort when you encounter movie rated higher then the actual, so in average case you'll do it less an less often, while progressing. You'll sort on every movie only in worst case scenario of having initial list sorted from lowest rated.
Here’s what I would do:
// let’s say get_next_movie () returns array with 'rating' and 'name' keys
while ($m = get_next_movie ()) {
$ratings[$m['rating']][] = $m['movie'];
$temp_ratings = $ratings;
$top5 = array ();
$rating = 5;
while (1) {
if (count ($temp_ratings[$rating])) {
$top5[] = array_shift ($temp_ratings[$rating]);
} elseif ($rating > 0) {
--$rating;
} else {
break;
}
}
// $top5 has current top 5 :-)
}
$ratings array looks like this, each rating has array of movies inside:
Array
(
[5] => Array
(
[0] => Five!
)
[3] => Array
(
[0] => Three
[1] => Threeeeee
[2] => Thr-eee-eee
)
[4] => Array
(
[0] => FOR
)
)
Maybe this can be of help.
class TopList {
private $items = array();
private $indexes = array();
private $count = 0;
private $total = 5;
private $lowest;
private $sorted = false;
public function __construct($total = null) {
if (is_int($total))
$this->total = $total;
$this->lowest = -1 * (PHP_INT_MAX - 1);
}
public function addItem($index, $item) {
if ($index <= $this->lowest)
return;
$setLowest = $this->count === $this->total;
if ($setLowest) {
/* //remove first added
$lowestIndex = array_search($this->lowest, $this->indexes);
/*/ //remove last added
$lowestIndex = end(array_keys($this->indexes, $this->lowest));
//*/
unset($this->indexes[$lowestIndex], $this->items[$lowestIndex]);
} else {
++$this->count;
$setLowest = $this->count === $this->total;
}
$this->indexes[] = $index;
$this->items[] = $item;
$this->sorted = false;
if ($setLowest)
$this->lowest = min($this->indexes);
}
public function getItems() {
if (!$this->sorted) {
array_multisort($this->indexes, SORT_DESC, $this->items);
$this->sorted = true;
}
return $this->items;
}
}
$top5 = new TopList(5);
foreach ($movies as $movie) {
$top5->addItem($movie['rating'], $movie);
}
var_dump($top5->getItems());