Jump to content

Sort array based on sum of 2 values


ChenXiu

Recommended Posts

$data is an array of customers.
This array shows the Customer's name, the item price, and the postage cost.
The total cost will be "item price + postage cost."
How do I sort this array based on the total cost? (the sum of postage and item)

print_r($data);

// prints this in my browser:

Array
(
    [listings] => Array
        (
            [0] => Array
                (
                    [item_price] => Array
                        (
                            [value] => 60.00
                        )

                    [postage] => Array
                        (
                            [price] => Array
                                (
                                    [value] => 20.00
                                )

                        )

                    [customer] => Array
                        (
                            [name] => Barbara
                        )

                )

             [1] => Array
                (
                    [item_price] => Array
                        (
                            [value] => 40.00
                        )

                    [postage] => Array
                        (
                            [price] => Array
                                (
                                    [value] => 30.00
                                )

                        )

                    [customer] => Array
                        (
                            [name] => Fred
                        )

                )

            [2] => Array
                (
                    [item_price] => Array
                        (
                            [value] => 70.00
                        )

                    [postage] => Array
                        (
                            [price] => Array
                                (
                                    [value] => 20.00
                                )

                        )

                    [customer] => Array
                        (
                            [name] => Julie
                        )

                )


            )

)

The result should be from lowest total to highest total.
I tried this, but only got errors:
$price = array_column($data["listings"]["item_price"] + $data["listings"]["postage"]);
array_multisort( $price , SORT_ASC , $data["listings"] );

Am I going about this correctly?

Thank you.

Link to comment
Share on other sites

Thank you, but I still can't make it work with my specific array.
To get to the item price, and delivery price, I need to loop through the array, and I cannot seem to do this without errors.

To demonstrate that I am really trying......

I believe this gets me to the relevant part:
foreach($data["listings"] as $each) {

....and once I'm there, then I have $each["item_price"]["value"]
and $each["postage"]["price"]["value"]

And therefore, the total item cost will be:
$total_price = $each["item_price"]["value"] + $each["postage"]["price"]["value"];

After that, I have no clue..... I am forcing myself to learn Arrays, but there comes a point where I must ask for help when I get totally stumped.

Thank you.

 

Link to comment
Share on other sites

You were advised to use usort(). Your custom function would compare the totals of each pair of records ($a and $b) and return a negative value if $a should sort before $b or positive if $a sorts after $b. (ie total A - total B)

$listings = $data['listings'];
usort($listings, function($a, $b) {
                     return $a['item_price']['value'] + $a['postage']['price']['value'] - $b['item_price']['value'] - $b['postage']['price']['value'];
                 });
echo '<pre>' . print_r($listings, 1) . '</pre>';

 

  • Great Answer 1
Link to comment
Share on other sites

@Barand, that works! Thank you!
Yes, I was advised to use usort()
....BUT
I quickly found that usort (like sort, array_multisort, et al) requires the programmer have a basic understanding of arrays (e.g. array keys, key/value pairs, etc.), and this is the hardest concept for me to wrap my brain around.

My experience of seeing array code such as yours is akin to shock and awe a non-technologically advanced tribal villager experiences when they see a satellite..... or a cellphone for the first time.
Or when the UFO crashed at Roswell and army personnel were presented with anti-gravity technology.........

"How the h*** do they know this stuff?"

I've started from the basics, "Arrays 101," but the teaching examples are dumbed down, e.g.
$array = array('a' , 'b' , 'c');

"This is how you can magically count how many elements are in $array: use 'array_count'"
Wow, how useful. Not.

And then if I go onto google / Stack Overflow and type keywords like "sort array based on sum" (like I did 1000 times already), there are these convoluted questions from people with ridiculous arrays (usually of sports teams and their stats, etc., or some professor trying to keep track of their students in Excel, etc.), and NO WAY to extrapolate clues from the "voted best answer" to apply to my own array question.

Like the dog who really doesn't understand the relationship between the owner, the can opener sound, and the bowl of food that appears moments later, I really have no clue how you knew to start with "$listings = $data['listings']," but I'm happy it's now sitting in front of me. 😀

 

Link to comment
Share on other sites

Interesting:
It appears that usort may not sort as anticipated when using decimal numbers (e.g. a dollar amount of $18.53 in my "item price + postage cost" example above).

Multiplying by 100 seems to fix it.
return (100 * $a['item_price']['value']) + (100 * $a['postage']['price']['value']) - (100 * $b['item_price']['value']) - (100 * $b['postage']['price']['value']);

Link to comment
Share on other sites

Works for me with decimal values.

<?php
$data = [
           [ 'price' => 8.50, 
             'postage' => 3.50
           ],
           [ 'price' => 2.50, 
             'postage' => 1.50
           ],
           [ 'price' => 7.50, 
             'postage' => 2.10
           ],
           [ 'price' => 10.00, 
             'postage' => 1.50
           ],
           [ 'price' => 2.50, 
             'postage' => 1.80
           ]
        ];
        
usort($data, function($a, $b) {
                  return $a['price'] + $a['postage'] - $b['price'] - $b['postage'];
              });
echo '<pre>' . print_r($data, 1) . '</pre>';  
?>

results

Array
(
    [0] => Array
        (
            [price] => 2.5
            [postage] => 1.5
        )

    [1] => Array
        (
            [price] => 2.5
            [postage] => 1.8
        )

    [2] => Array
        (
            [price] => 7.5
            [postage] => 2.1
        )

    [3] => Array
        (
            [price] => 8.5
            [postage] => 3.5
        )

    [4] => Array
        (
            [price] => 10
            [postage] => 1.5
        )

)

 

Link to comment
Share on other sites

When there is zero, the result is incorrect:

<?php
$data = [
           [ 'price' => 8.50, 
             'postage' => 3.50
           ],
           [ 'price' => 6.18, 
             'postage' => 2.33
           ],
           [ 'price' => 6.13, 
             'postage' => 0
           ],
           [ 'price' => 4.19, 
             'postage' => 2.63
           ],
           [ 'price' => 2.50, 
             'postage' => 1.80
           ]
        ];
        
usort($data, function($a, $b) {
                  return $a['price'] + $a['postage'] - $b['price'] - $b['postage'];
              });
echo '<pre>' . print_r($data, 1) . '</pre>';  
?>

 

Link to comment
Share on other sites

Dogs and cats fix the error:

<?php
$data = [
           [ 'price' => 8.50, 
             'postage' => 3.50
           ],
           [ 'price' => 6.49, 
             'postage' => 0
           ],
           [ 'price' => 6.10, 
             'postage' => 0
           ],
           [ 'price' => 10.00, 
             'postage' => 1.50
           ],
           [ 'price' => 2.50, 
             'postage' => 1.80
           ]
        ];
        
usort($data, function($a, $b) {
	$dog = $a['price'] + $a['postage'];
	$cat = $b['price'] + $b['postage'];
  return $dog - $cat;
});
echo '<pre>' . print_r($data, 1) . '</pre>';  
?>

 

Edited by ChenXiu
Link to comment
Share on other sites

This specific array causes the problem:

<?php
$data = [
           [ 'price' => 8.50, 
             'postage' => 3.50
           ],
           [ 'price' => 6.18, 
             'postage' => 2.33
           ],
           [ 'price' => 6.13, 
             'postage' => 0
           ],
           [ 'price' => 4.19, 
             'postage' => 2.63
           ],
           [ 'price' => 2.50, 
             'postage' => 1.80
           ]
        ];
        
usort($data, function($a, $b) {
                  return $a['price'] + $a['postage'] - $b['price'] - $b['postage'];
              });
echo '<pre>' . print_r($data, 1) . '</pre>';  // $data[1] and $data[2] are in wrong position
?>

$data[1] and $data[2] are not in the correct position.

But if I do something like the following code, then the result is correct:

<?php
$data = [
           [ 'price' => 8.50, 
             'postage' => 3.50
           ],
           [ 'price' => 6.18, 
             'postage' => 2.33
           ],
           [ 'price' => 6.13, 
             'postage' => 0
           ],
           [ 'price' => 4.19, 
             'postage' => 2.63
           ],
           [ 'price' => 2.50, 
             'postage' => 1.80
           ]
        ];
      
usort($data, function($a, $b) {
$cat = $a['price'] + $a['postage'];
$dog = $b['price'] + $b['postage'];
if($cat > $dog) {
$result = 1;
} elseif ($cat < $dog) {
$result = -1;
}
return $result;
});
echo '<pre>' . print_r($data, 1) . '</pre>';  // The result order is now correct.
?>

 

Edited by ChenXiu
Link to comment
Share on other sites

I modified the code slightly tot add a 'total' element to each of the arrays to make sequence order easier to check.

Revised code

<?php
    $data = [
           [ 'price' => 8.50, 
             'postage' => 3.50
           ],
           [ 'price' => 6.18, 
             'postage' => 2.33
           ],
           [ 'price' => 6.13, 
             'postage' => 0
           ],
           [ 'price' => 4.19, 
             'postage' => 2.63
           ],
           [ 'price' => 2.50, 
             'postage' => 1.80
           ]
        ];

// add total element to arrays
foreach ($data as &$a) {
    $a['total'] = $a['price'] + $a['postage'];
}
       
usort($data, function($a, $b) {
                  return $a['price'] + $a['postage'] - $b['price'] - $b['postage'];
              });
echo '<pre>' . print_r($data, 1) . '</pre>';  // $data[1] and $data[2] are in wrong position

?>

My results

Array
(
    [0] => Array
        (
            [price] => 2.5
            [postage] => 1.8
            [total] => 4.3
        )

    [1] => Array
        (
            [price] => 6.13
            [postage] => 0
            [total] => 6.13
        )

    [2] => Array
        (
            [price] => 4.19
            [postage] => 2.63
            [total] => 6.82
        )

    [3] => Array
        (
            [price] => 6.18
            [postage] => 2.33
            [total] => 8.51
        )

    [4] => Array
        (
            [price] => 8.5
            [postage] => 3.5
            [total] => 12
        )

)

The order definitely looks correct.

Link to comment
Share on other sites

Sure, no problem. Here is the script:

<?php
$data = [
           [ 'price' => 8.50, 
             'postage' => 3.50
           ],
           [ 'price' => 4.18, 
             'postage' => 0
           ],
           [ 'price' => 5.25, 
             'postage' => .17
           ],
           [ 'price' => 4.15, 
             'postage' => .02
           ],
           [ 'price' => 2.50, 
             'postage' => 1.80
           ]
        ];
        
usort($data, function($a, $b) {
                  return $a['price'] + $a['postage'] - $b['price'] - $b['postage'];
              });
echo '<pre>' . print_r($data, 1) . '</pre>';  // $data[1] and $data[2] are in wrong position
?>

Here is the result:

<pre>Array
(
    [0] => Array
        (
            [price] => 4.18
            [postage] => 0
        )

    [1] => Array
        (
            [price] => 4.15
            [postage] => 0.02
        )

    [2] => Array
        (
            [price] => 2.5
            [postage] => 1.8
        )

    [3] => Array
        (
            [price] => 5.25
            [postage] => 0.17
        )

    [4] => Array
        (
            [price] => 8.5
            [postage] => 3.5
        )

)
</pre>

(I had to take some extra time to edit my post because the results are correct with certain numbers, but if I change certain digits, then the result will be wrong. It's "hit and miss" and I finally found some numeric values that produce the error.

This error happens in both PHP 7.4, as well as PHP 8.

The only way I got it to sort correctly is to get some chew toys and catnip and do:

usort($data, function($a, $b) {
$cat = $a['price'] + $a['postage'];
$dog = $b['price'] + $b['postage'];
if($cat > $dog) {
$result = 1;
} elseif ($cat < $dog) {
$result = -1;
}
return $result;
});

***NEWSFLASH***EXTRA EXTRA READ ALL ABOUT IT ******
CHENXIU FINDS BUG IN PHP8
GETS AWARDED SCHOLARSHIP
TO PHP ARRAY COLLEGE
*******************

Edited by ChenXiu
Link to comment
Share on other sites

1 hour ago, ChenXiu said:

***NEWSFLASH***EXTRA EXTRA READ ALL ABOUT IT ******
CHENXIU FINDS BUG IN PHP8
GETS AWARDED SCHOLARSHIP
TO PHP ARRAY COLLEGE
*******************

 

This is not a bug, but a limitation on how floating point numbers get represented in binary.  Some number cannot be perfectly represented so you end up with a close but not exact approximation.  As you do math with these close but not perfect numbers, the error can accumulate enough to cause issues.  The classic example is adding 0.1 and 0.2.

echo (0.1+0.2 === 0.3)?'Matching':'Not a match';

If you run that, you'll get the Not a match message despite your expectations.  This is due to the error accumulation of the math.  You can see the real result if you serialze the equation.

echo serialize(0.1+0.2);

The result of that equation is actually 0.30000000000000004.

In your case, the two arrays in question should be 0.01 difference, but the math ends up giving a difference of 0.00999999999999936 instead. I think the clearest way to solve the issue here is to add the two price/postage keys then round them to a precision of 2 and then compare them.  Solves the precision issue and reads easier.

usort($data, function($a, $b) {
    $aTotal=round($a['price']+$a['postage'],2);
    $bTotal=round($b['price']+$b['postage'],2);
    return $aTotal <=> $bTotal;
});

 

Edited by kicken
Link to comment
Share on other sites

On second examination, while the floating point limitations are something to consider, I think the issue here is simpler.

usort expects the function to return an integer.  As a result the small difference is simply being rounded to 0, and 0 means the two entries are considered equal.  The reason your re-write works is because you're using 1 / -1 (but not accounting for 0/equal which is something to be fixed) and mine works because it uses the spaceship operator which returns an appropriate integer value.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.