need advice on comparing values of nested arrays.

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

visitor-Q
Forum Commoner
Posts: 72
Joined: Mon Feb 05, 2007 1:40 am

need advice on comparing values of nested arrays.

Post by visitor-Q »

a little background:
i'm building a pseudo-shopping cart for a client of mine. i say 'pseudo' because there is no database or cms backend for this system. all the items will be hard-coded in HTML forms. the shopping cart items will be stored entirely in a session as any normal shopping cart handles them.

each item will have a few pre-defined values such as id, price, name, quantity, and an array called itemInfo. for example, let's say one of the item's is a sandwitch. the sandwitch comes standard with lettuce, tomatoes, and cheese. these options will be the pre-checked options in the sandwitch form. however, the user can uncheck these options, and check other options such as 'mayonaise' or 'pickles' for example. so imagine these variables are stored in hidden input fields for the sandwitch form:

Code: Select all

$id = 1;
$name = 'sandwitch';
$price = 6.50;
$quantity = 1;
$itemInfo = array('23' => 'lettuce', '12' => 'tomatoes', '43' => 'mayonaise');
now here's where it gets confusing for me... i need to write a function to check all the items of the shopping cart to FIRST see if there is a repeat of the same item in the shopping cart (for instance if the user added the sandwitch twice), THEN if there is more than one of the same item in the shopping cart, compare their $itemInfo array to see if they are the same... if they are, delete one of the two items, and add 1 to the quantity of the first item.

the reason why i need this is because there shouldn't be 2 separate sandwitches listed in the shopping cart, if they are the same sandwitch. it should only have one entry, and show a quantity of 2. but the user can add 2 sandwitches, but have different things on the sandwitch, in which case there would be 2 separate sandwitches in the cart and their contents listed below each one.

does that make sense? if there is any more information you guys need to know, just let me know. i'll do my best to explain the situation as detailed as possible so i can be led in the right direction.

i haven't begun coding, yet, i want to get the logic down before hand. i'm sure i'm going to be using array_diff_assoc and while loops and foreach loops, but i just don't know how and in what order. i'm not looking for anyone to code it for me, just a little help on the logistics. thanks.


EDIT:
just a little bit more info on the situation. the contents of the cart will be stored in this manner:

Code: Select all

$_SESSION['cart']
	|
	+----[item1]
	|	|
	|	+-----[id] = 1
	|	|
	|	+-----[name] = 'sandwitch'
	|	|
	|	+-----[price] = 6.50
	|	|
	|	+-----[quantity] = 1
	|	|
	|	+-----[itemInfo]
	|		   |
	|		   +-----[lettuce]
	|		   |
	|		   +-----[tomatoes]
	|		   |
	|		   +-----[cheese]
	|
	+----[item2]
	|	|
	|	+-----[id] = 1
	|	|
	|	+-----[name] = 'sandwitch'
	|	|
	|	+-----[price] = 6.50
	|	|
	|	+-----[quantity] = 1
	|	|
	|	+-----[itemInfo]
	|		   |
	|		   +-----[lettuce]
	|		   |
	|		   +-----[tomatoes]
	|		   |
	|		   +-----[cheese]
	|		   |
	|		   +-----[mayonaise]
	|
	+----[item3]
		|
		+-----[id] = 1
		|
		+-----[name] = 'sandwitch'
		|
		+-----[price] = 6.50
		|
		+-----[quantity] = 1
		|
		+-----[itemInfo]
			   |
			   +-----[lettuce]
			   |
			   +-----[mustard]
			   |
			   +-----[cheese]
but if there are two sandwitches with the same exact itemInfo:

Code: Select all

$_SESSION['cart']
	|
	+----[item1]
	|	|
	|	+-----[id] = 1
	|	|
	|	+-----[name] = 'sandwitch'
	|	|
	|	+-----[price] = 6.50
	|	|
	|	+-----[quantity] = 2
	|	|
	|	+-----[itemInfo]
	|		   |
	|		   +-----[lettuce]
	|		   |
	|		   +-----[tomatoes]
	|		   |
	|		   +-----[cheese]
   |
	+----[item2]
		|
		+-----[id] = 1
		|
		+-----[name] = 'sandwitch'
		|
		+-----[price] = 6.50
		|
		+-----[quantity] = 1
		|
		+-----[itemInfo]
			   |
			   +-----[lettuce]
			   |
			   +-----[tomatoes]
			   |
			   +-----[cheese]
			   |
			   +-----[pickles]
does that make sense?
visitor-Q
Forum Commoner
Posts: 72
Joined: Mon Feb 05, 2007 1:40 am

Post by visitor-Q »

an update, i've decided to use what's called a 'binary' method to check the itemInfo array. similar to the CHMOD command in unix systems.
chmod man page.

basically what i mean is, chmod uses only 3 digits (1, 2, and 4) to identify the user permissions. adding these values together by assigning each one to it's own checkbox will change the total value of all the item's itemInfo array. so, all i have to do, is compare the total value of each itemInfo if there is more than one of the same item in the cart. does that make sense?
visitor-Q
Forum Commoner
Posts: 72
Joined: Mon Feb 05, 2007 1:40 am

Post by visitor-Q »

to better explain my previous post i plan on doing this with each item in my item list.

Code: Select all

<?php
        session_start();

        echo "
                Sandwitch:
                <form action=\"cart.php\" method=\"post\">
                <input type=\"hidden\" name=\"sandwitch\" value=\"13\">
                <input type=\"hidden\" name=\"price\" value=\"6.50\">
                <input type=\"hidden\" name=\"quantity\" value=\"1\">
        ";

        $attributes = array('lettuce', 'cheese', 'tomatoes',
                             'mayonaise', 'pickles', 'mustard',
                             'onions', 'ketchup');

        $i = 1;
        foreach($attributes as $attribute){
                echo "<input type=\"checkbox\" name=\"{$checked[]}\" value=\"{$i}\"";
                (($attribute == 'lettuce') ? (" CHECKED") : (""));
                (($attribute == 'tomatoes') ? (" CHECKED") : (""));
                (($attribute == 'cheese') ? (" CHECKED") : (""));
                echo ">{$attribute}\n";
                $i *= 2;
        }

        echo "
                <input type=\"submit\" value=\"Submit\">
                </form>
        ";
?>
then, cart.php will handle it something like this:

Code: Select all

<?php
        session_start();

        if(isset($_POST)){
               /*
                * i'm not sure if this will overwrite the first index,
                * or if it will append the new item into a new index.
                * i want it to append.
                */
                $_SESSION['shopping_cart'] = $_POST;
        }

        $comp_item_attributes = array();
        foreach($_SESSION['shopping_cart'] as $item){
                $total_binary = 0;
                foreach($item['checked'] as $binary){
                        $total_binary += $binary;
                }
                $bool = false;
                foreach($comp_item_attributes as $name => $val){
                        if($item['name'] == $name){
                                $bool = true;
                                if($total_binary == $val){
                                        $item['quantity']++;
                                }
                        }
                }
                if(bool = false){
                         $comp_item_attributes = $comp_item_attributes[$name => $total_binary];
                }
        }
?>
and that's all i got so far. i'm pretty sure the last few lines aren't logically correct and the rest is a little confusing, so i'm going to work on it later and post the newer (hopefully more efficient) version. in the meantime do you guys have any suggestions?
Last edited by visitor-Q on Fri Mar 30, 2007 9:24 am, edited 1 time in total.
nickvd
DevNet Resident
Posts: 1027
Joined: Thu Mar 10, 2005 5:27 pm
Location: Southern Ontario
Contact:

Post by nickvd »

I have no idea if it will work, but you can try:

Code: Select all

if (serialize($arrayOne) === serialize($arrayTwo)) echo "Items are identical";
User avatar
stereofrog
Forum Contributor
Posts: 386
Joined: Mon Dec 04, 2006 6:10 am

Post by stereofrog »

Using binary arithmetic for options is a good idea. This is how you can go about it.

1. give the options numeric values that are powers of two: lettuce = 2, cheese = 4, tomatoes = 8, mayonaise = 16 etc

2. to combine two options together use binary or ( | ) operator, for example, 10 (= 2 | 8) means "lettuce and tomatoes"

3. to check if an option is turned on, use binary and ( & ) operator, e.g. "if(options & 8) tomatoes are included"

4. for the "cart" array use product_id + some separator + options as a key, so that you can check items without having to search.

Example. The user chooses "sandwitch" (prod id 25) with lettuce (=2) and mayonaise (=16). The options value becomes 2|16 = 18 and the compound key is 25 . '~' . 18 = "25~18". Having this, check and increment code becomes very simple:

Code: Select all

if(isset($_SESSION['cart'][$key]))
   $_SESSION['cart'][$key]++;
else
   $_SESSION['cart'][$key] = 1;
Hope this helps.
visitor-Q
Forum Commoner
Posts: 72
Joined: Mon Feb 05, 2007 1:40 am

Post by visitor-Q »

nickvd wrote:I have no idea if it will work, but you can try:

Code: Select all

if (serialize($arrayOne) === serialize($arrayTwo)) echo "Items are identical";
i must have been stoned last night because that makes much more sense than using a foreach. however, ontop of your suggestion, i think it's a good idea to sort them first, as well...

Code: Select all

if(serialize(sort($arrayOne)) === serialize(sort($arrayTwo))) echo "Items are identical";
wouldn't you agree? and if i'm going to be sorting them, why would i need to compare the total added binaries of each item's attributes? if these arrays are sorted and then compared, i could just use something like:

Code: Select all

$diff = array_diff(sort($array1), sort($array2));
if(!empty($diff)) echo "items are different";
if i'm correct, that would be just as effective and relieve the need for binary comparison.


@sterefrog - i'm going to read your post a few times and see if i can grasp the concept a little better. thanks for the post! :)
visitor-Q
Forum Commoner
Posts: 72
Joined: Mon Feb 05, 2007 1:40 am

Post by visitor-Q »

so here's the updated code so far:

itemTemplate.php

Code: Select all

<?php
        session_start();

        function populateForm($name, $id, $price, $quantity, $standard_attributes, $all_attributes){
                echo "
                        <b>{$name}</b>
                        <form action=\"cart.php\" method=\"post\">
                        <input type=\"hidden\" name=\"{$name}\" value=\"{$id}\">
                        <input type=\"hidden\" name=\"price\" value=\"{$price}\">
                        <input type=\"hidden\" name=\"quantity\" value=\"{$quantity}\">

                \n";

                foreach($all_attributes as $a_attribute){
                        echo "\t\t\t<input type=\"checkbox\" name=\"checked[]\" value=\"{$a_attribute}\"";
                                foreach($standard_attributes as $s_attribute){
                                        (($s_attribute == $a_attribute) ? (" CHECKED") : (""));
                                }
                        echo ">{$a_attribute}\n";
                }

                echo "
                        <input type=\"submit\" value=\"Add to Cart\">
                        </form>
                \n";
        }

        $all_sandwich_attributes = array('mustard', 'mayonnaise', 'pickles',
                                           'lettuce', 'tomatoes', 'cheese');

        $standard_sandwich_attributes = array('lettuce', 'tomatoes', 'cheese');

        populateForm('Sandwich', 12, 6.50, 1, $standard_sandwich_attributes, $all_sandwich_attributes);
?>
cart.php

Code: Select all

<?php
        session_start();

        echo "<b>SESSION ARRAY:</b>\n<pre>\n"; print_r($_SESSION); echo "</pre>\n";
        echo "<b>POST ARRAY:</b>\n<pre>\n"; print_r($_POST); echo "</pre>\n";

        if(isset($_POST) && !empty($_POST)){
                $_SESSION['shopping_cart'][] = $_POST;
                unset($_POST);
        }

        $comp_item_attributes = array();
        foreach($_SESSION['shopping_cart'] as $item){
                foreach($item['checked'] as $binary){
                        $total_binary += $binary;
                }
                $bool = false;
                foreach($comp_item_attributes as $name => $val){
                        if($item['name'] == $name){
                                $bool = true;
                                if($total_binary == $val){
                                        $item['quantity']++;
                                }
                        }
                }
                if($bool == false){
                         $comp_item_attributes = $comp_item_attributes['{$name} => {$total_binary}'];
                }
        }
?>
i'm still having trouble with the logistics of comparing every item's attributes in the shopping cart with the rest of the items attributes that match the same item name. i've been doing a little research, and i think i'm gonna end up using some nested foreach loops with a combination of current(), next(), prev(), and end(). but i'm not sure if that's the most efficient way of doing it. i'm really confused about how to approach this particular feature of the shopping cart, can i get any suggestions?
User avatar
stereofrog
Forum Contributor
Posts: 386
Joined: Mon Dec 04, 2006 6:10 am

Post by stereofrog »

visitor-Q wrote:can i get any suggestions?
Yes. Try to think, try to read, try to hear what other people say.
What you're trying to do is fairly simple and can be coded with a few lines of php.
visitor-Q
Forum Commoner
Posts: 72
Joined: Mon Feb 05, 2007 1:40 am

Post by visitor-Q »

so here's the item template again with some minor changes to it:

Code: Select all

<?php
        session_start();

        function populateForm($name, $id, $price, $quantity, $standard_attributes, $all_attributes){
                echo "
                        <b>{$name}</b>
                        <form action=\"cart.php\" method=\"post\">
                        <input type=\"hidden\" name=\"{$name}\" value=\"{$id}\">
                        <input type=\"hidden\" name=\"price\" value=\"{$price}\">
                        <input type=\"hidden\" name=\"quantity\" value=\"{$quantity}\">

                \n";

                foreach($all_attributes as $a_attribute){
                        echo "\t\t\t<input type=\"checkbox\" name=\"attributes[]\" value=\"{$a_attribute}\"";
                                foreach($standard_attributes as $s_attribute){
                                        (($s_attribute == $a_attribute) ? (" CHECKED") : (""));
                                }
                        echo ">{$a_attribute}\n";
                }

                echo "
                        <input type=\"submit\" value=\"Add to Cart\">
                        </form>
                \n";
        }

        $all_sandwich_attributes = array('mustard', 'mayonnaise', 'pickles',
                                           'lettuce', 'tomatoes', 'cheese');

        $standard_sandwich_attributes = array('lettuce', 'tomatoes', 'cheese');

        populateForm('Sandwich', 12, 6.50, 1, $standard_sandwich_attributes, $all_sandwich_attributes);
?>

and here's what i got for the cart handler, so far:

Code: Select all

<?php
        session_start();

        echo "<b>SESSION ARRAY:</b>\n<pre>\n"; print_r($_SESSION); echo "</pre>\n";
        echo "<b>POST ARRAY:</b>\n<pre>\n"; print_r($_POST); echo "</pre>\n";

        if(isset($_POST)){
                foreach($_SESSION['shopping_cart'] as $cart_item){
                        foreach($cart_item['attributes'] as $cart_attributes){
                                if(sort($_POST['attributes']) == sort($cart_attributes)){
                                        $cart_item['quantity'] += $_POST['quantity'];
                                }else{
                                        $_SESSION['shopping_cart'][] = $_POST;
                                }
                        }
                }
                unset($_POST);
        }
?>
it's throwing the error invalid argument on the second foreach loop. i'm pretty sure i'm relatively close to completing this feature of the shopping cart, i just don't know how to provide the argument i want in the proper fashion. if you take a look at my code you should be able to follow what i'm trying to do with it... any suggestions? anybody?
nickvd
DevNet Resident
Posts: 1027
Joined: Thu Mar 10, 2005 5:27 pm
Location: Southern Ontario
Contact:

Post by nickvd »

if $cart_item is not an array then the nested foreach will puke.
visitor-Q
Forum Commoner
Posts: 72
Joined: Mon Feb 05, 2007 1:40 am

Post by visitor-Q »

nickvd wrote:if $cart_item is not an array then the nested foreach will puke.
yeah that's what i figured, but each index in the shopping cart holds an array of the item, and within that item array there's another index that holds an array with the item's attributes. that cart handler was giving me some really weird results. here's what it is now.

Code: Select all

<?php
        session_start();

        echo "<b>SESSION ARRAY:</b>\n<pre>\n"; print_r($_SESSION); echo "</pre>\n";
        echo "<b>POST ARRAY:</b>\n<pre>\n"; print_r($_POST); echo "</pre>\n";

        if(isset($_POST)){
                if(!empty($_SESSION['shopping_cart'])){
                        foreach($_SESSION['shopping_cart'] as $cart_item){
                                if(sort($_POST['attributes']) == sort($cart_item['attributes'])){
                                        $cart_item['quantity'] += $_POST['quantity'];
                                }else{
                                        $_SESSION['shopping_cart'][] = $_POST;
                                }
                        }
                }else{
                        $_SESSION['shopping_cart'][] = $_POST;
                }
                unset($_POST);
        }
?>
but it doesn't work the way i want it to. first of all, when i post the form template and it takes me to cart.php, the $_SESSION array doesn't have anything, and neither does the $_POST array. then i 'refresh' the page and the $_SESSION array has one index with nothing in it, and the $_POST array has the item that was submitted. also, unset($_POST) isn't working at all. if it was, it wouldn't add another item into the cart every time i refresh. the sorting through the matching item attributes doesn't work, either. if it was, it wouldn't add another item, it'd just add to the quantity. instead it throws this error:
sort() expects parameter 1 to be array, null given

it looks like it will work logistically, but apparently i'm doing something wrong because none of it works! i checked the manual for the sort() function and it said that it may handle arguments in an unexpected manner. does anyone have any suggestions to an alternative sort function thay may be better for this feature?

fyi: that is the only error it's throwing as of now. all other errors have been handled
visitor-Q
Forum Commoner
Posts: 72
Joined: Mon Feb 05, 2007 1:40 am

Post by visitor-Q »

i got rid of the error by adding the argument SORT_STRING to each sort function. but now, it will only add one item the the shopping cart if it was empty before hand. it won't add any more. no errors, it's just not functional code. this is what i have now.

Code: Select all

<?php
        session_start();

        echo "<b>SESSION ARRAY:</b>\n<pre>\n"; print_r($_SESSION); echo "</pre>\n";
        echo "<b>POST ARRAY:</b>\n<pre>\n"; print_r($_POST); echo "</pre>\n";

        if(isset($_POST)){
                if(!empty($_SESSION['shopping_cart'])){
                        $bool = false;
                        foreach($_SESSION['shopping_cart'] as $cart_item){
                                if(sort($_POST['attributes'], SORT_STRING) == sort($cart_item['attributes'], SORT_STRING)){
                                        $cart_item['quantity'] += $_POST['quantity'];
                                        $bool = true;
                                }
                        }
                        if($bool != true){ $_SESSION['shopping_cart'][] = $_POST; }
                }
                unset($_POST);
        }
?>
could somebody please help me???
nickvd
DevNet Resident
Posts: 1027
Joined: Thu Mar 10, 2005 5:27 pm
Location: Southern Ontario
Contact:

Post by nickvd »

I would: (in pseudo code)

Code: Select all

//form has been posted

$newItem = //Built to match standard layout via $_POST
$newSerial = serialize($newItem);


foreach ($items as $item) {
  if (serialize($item) === $newSerial) {
    //items are equal, increase quantity by one
  } else {
    //add new item into cart
  }
}
visitor-Q
Forum Commoner
Posts: 72
Joined: Mon Feb 05, 2007 1:40 am

Post by visitor-Q »

nickvd wrote:I would: (in pseudo code)

Code: Select all

//form has been posted

$newItem = //Built to match standard layout via $_POST
$newSerial = serialize($newItem);


foreach ($items as $item) {
  if (serialize($item) === $newSerial) {
    //items are equal, increase quantity by one
  } else {
    //add new item into cart
  }
}

but you see your code is flawed... because for every item in the shopping cart that the new item doesn't match, it's going to add that item, and then, it creates an infinite loop... it has to search through every item in the shopping cart for a match first, and then determine if it needs to add it to the database, not while it's searching...
visitor-Q
Forum Commoner
Posts: 72
Joined: Mon Feb 05, 2007 1:40 am

Post by visitor-Q »

here's what i've got now:
itemTemplate.php

Code: Select all

<?php
        session_start();

        /*if this is the first page the user sees, begin the register the session id now*/
        if(!isset($_SESSION['session_id'])){ $_SESSION['session_id'] = session_register(); }

        function populateForm($name, $id, $price, $quantity, $standard_ingredients, $all_ingredients){
                echo "
                        <b>{$name}</b>
                        <form action=\"cart.php\" method=\"post\">
                        <input type=\"hidden\" name=\"id\" value=\"{$id}\">
                        <input type=\"hidden\" name=\"name\" value=\"{$name}\">
                        <input type=\"hidden\" name=\"price\" value=\"{$price}\">
                        <input type=\"hidden\" name=\"quantity\" value=\"{$quantity}\">
                \n";

                foreach($all_ingredients as $a_ingredient){
                        echo "\t\t\t<input type=\"checkbox\" name=\"ingredients[]\" value=\"{$a_ingredient}\"";
                                foreach($standard_ingredients as $s_ingredient){
                                        (($s_ingredient == $a_ingredient) ? (" CHECKED") : (""));
                                }
                        echo ">{$a_ingredient}\n";
                }

                echo "
                        <br /><b>$". number_format($price, 2) ."</b>
                        <br /><input type=\"submit\" value=\"Add to Cart\">
                        </form>
                \n";
        }

        $all_sandwich_ingredients = array('mustard', 'mayonnaise', 'pickles',
                                          'lettuce', 'tomatoes', 'cheese');

        $standard_sandwich_ingredients = array('lettuce', 'tomatoes', 'cheese');

        populateForm('Sandwich', 12, 6.50, 1, $standard_sandwich_ingredients, $all_sandwich_ingredients);
?>
cart.php

Code: Select all

<?php
        session_start();

        /*if this is the first page the user sees, begin the register the session id now*/
        if(!isset($_SESSION['session_id'])){ $_SESSION['session_id'] = session_register(); }

        /* print $_SESSION data*/
#        echo "<b>SESSION ARRAY:</b>\n<pre>\n"; print_r($_SESSION); echo "</pre>\n";

        /* print $_POST data*/
#        echo "<b>POST ARRAY:</b>\n<pre>\n"; print_r($_POST); echo "</pre>\n";

       /*
        * if the user is sent to this page from submiting an item
        * if the shopping cart is empty, there are no items to 
        * compare with. so, it won't proceed, it will just add
        * it as the first submitted item to the cart. if there
        * are items in the cart, we will assume that the $_POSTed
        * item is not identical to any items in the cart. now,
        * we run through each item in the cart one by one. we
        * first compare the id's of the shopping cart item that
        * is being evaluated and the item that was $_POSTed. if
        * the id's do match, sort their item attributes. then,
        * compare the arrays. if the attributes match, do not add
        * the $_POSTed item into a new index in the cart array,
        * just add the quantity of the $_POSTed item to the
        * existing identical item already in the cart. if
        * the items are not identical, add the $_POSTed item
        * into a new index in the cart array. unset the $_POST
        * array so if the user 'refreshes' the page, it will not
        * add the item again.
        */
        if(isset($_POST)){
                if(!empty($_SESSION['shopping_cart'])){
                        foreach($_SESSION['shopping_cart'] as $cart_item){
                                if($cart_item['id'] == $_POST['id']){
                                        if(sort($_POST['attributes'], SORT_STRING) === sort($cart_item['attributes'], SORT_STRING)){
                                                $cart_item['quantity'] += $_POST['quantity'];
                                        }else{ $_SESSION['shopping_cart'][] = $_POST; }
                                }
                        }
                }else{ $_SESSION['shopping_cart'][] = $_POST; }
                unset($_POST);
        }
?>
issues i am having with this code:
itemTemplate.php : item attributes will not pre-check themselves.

cart.php : after i add an item to the cart, if the cart was empty, it adds the item just fine. but if there is an item in the cart, it will not add it to the cart no matter what.

cart.php : unset($_POST) will not unset the array.

cart.php : it will not register a session id. instead there is a randomly generated randomly placed session id already in the cart array for some reason...

this is what cart.php prints:

Code: Select all

SESSION ARRAY:

Array
(
    [session_id] => 
    [shopping_cart] => Array
        (
            [0] => Array
                (
                    [PHPSESSID] => 79d7f7a243e77fcc1295c9828aa221e9
                    [Sandwich] => 12
                    [price] => 6.5
                    [quantity] => 1
                    [ingredients] => Array
                        (
                            [0] => pickles
                            [1] => tomatoes
                        )

                )

        )

)

POST ARRAY:

Array
(
    [id] => 12
    [name] => Sandwich
    [price] => 6.5
    [quantity] => 1
    [ingredients] => Array
        (
            [0] => cheese
        )

)

any suggestions? i can't get any more clear than that.
Post Reply