How do array_udiff(), array_uintersect() etc. work? - php

There are a couple of similar questions here on SO, but I still haven't found an answer.
PHP manual states that
The comparison function must return an integer less than, equal to, or
greater than zero if the first argument is considered to be
respectively less than, equal to, or greater than the second.
The thing is that I don't understand why is it so important to have three optional outcomes, like -1, 0 and 1? Why wouldn't it work out if I used 0 for cases when two values being compared are equal and 1 otherwise?
A more practical question: I have a task where I should find elements of the first array that exist in the second one. The structures of the arrays are the same and look like the following:
Array
(
[0] => Array
(
[productId] => 5479046275
[options] => Array
(
[1] => All
[2] => Green
)
)
)
So I consider elements of such arrays equal when productId values match, and when there's no difference between options respecting their keys. Comparing options should be performed with the fact that 'All' options match every other's value. So I have a code like this:
$cartItems = <MyArray>;
$triggers = <MyAnotherArray>;
/**
* Options equal each other when they are exactly the same or
* when bundle offer's product has 'All' selected as option.
*/
$compareOptions = function ($a, $b) {
if ('All' == $b) {
return 0;
}
return strcmp($a, $b);
};
/**
* Compare product id to make sure these variants are of the same product
* Then check the options. If there's no difference, then variants
* are equal
*/
$compareVariants = function ($a, $b) use ($compareOptions) {
if ($a['productId'] != $b['productId']) {
return ($a['productId'] > $b['productId']) ? 1 : -1;
}
$optionsDiff = array_udiff_assoc($a['options'], $b['options'], $compareOptions);
if (0 === count($optionsDiff)) {
return 0;
} else {
return (count($optionsDiff) > 0) ? 1 : -1;
}
};
return array_uintersect($cartItems, $triggers, $compareVariants);
Then I xDebugged it. For some reason, when $optionsDiff is empty and script returns 0, it doesn't not step out of $compareVariants function, but goes to $compareOptions comparing arrays which produces a error.
Why does it work like that?
Thanks.

array_udiff_assoc() will compare the keys using built-in function and values will be compared using user-defined function.
array_udiff_assoc($array1,$array2,"userDefinedFunction");
So no matter what the result is. The user defined function will be called automatically to compare the array values. If you don't want to call your 'compareOptions' function, then you can go for array_diff_assoc($array1,$array2). This will use built in function to compare both keys and values of the arrays.

Related

Sorting laravel collection by leaving null/empty last

Can't seem to get my head around of sorting laravel collection so empty / null data would end up being last. ( bit confused about usort )
Pretty much all I have is bunch of times / timestamps that need to be ordered. Some rows may not have for that column.
I would like data to appear ASC / ascending while empty/null data is shown last.
$collection->sortBy('timestamp') sorts nicely but doesn't know how to deal with empty fields.
Table looks like this.
$data = $data->sort(function($a, $b) use ($sortBy) {
if ($a->{$sortBy} and $b->{$sortBy}) return 0;
return ($a->{$sortBy} > $b->{$sortBy}) ? -1 : 1;
});
Random code I tried from the internet, which I can't get to work correctly.
$sortBy contains a field name to sort by ( since it may change )
Faulty code deals with empty / null data but its out of order.
Have to use sort() with a closure. Below will sort timestamp ASC with NULL at the end.
$sorted = $collection->sort(function ($a, $b) {
if (!$a->timestamp) {
return !$b->timestamp ? 0 : 1;
}
if (!$b->timestamp) {
return -1;
}
if ($a->timestamp == $b->timestamp) {
return 0;
}
return $a->timestamp < $b->timestamp ? -1 : 1;
});
Try:
$collection->sortBy('-timestamp')
Does it work?
I had a similar issue. In my case, the time attribute of a $result might be NULL. It acts as if NULL is 0 (as int) and that's expected behavior. But I also wanted to sort the collection by leaving NULL last.
$collection->sortBy(function ($result) {
if ($result['time'] === NULL) {
return PHP_INT_MAX;
}
return $result['time'];
});
You can achieve this simply by returning an value higher in the alphabetical order compared to all other values in the array. i.e. PHP_INT_MAX to be safe. This will make sure all the results where the time equals NULL are at the end of the array.
Similar to Mr. Speelman's solution, but as a shorter PHP 7.4+ version:
$collection->sortBy(fn($e) => $e->timestamp ?: PHP_INT_MAX)
I assume your timestamp is unix timestamp.
You can sort it like this :
$sorted = $collection->sortByDesc('timestamp');

Check if item exists in multiple arrays

I have a function which returns an array:
This is the fucntion:
{
global $woocommerce;
$items = $woocommerce->cart->get_cart();
$product_names=array();
foreach($items as $item => $values) {
$_product = $values['data']->post;
$product_names[]=$_product->post_title;
}
$allproductname=print_r($product_names, true);
return $allproductname;
}
The output of this function:
Array(
[0] => Social media campagne
[1] => Marketingcampagne
)
I also have 2 arrays named group1 and group2.
$group1=array("Social media campagne","Marketingcampagne");
$group2=array("SEO","Facebook marketing");
Now what i want to check is if both of the values of the function output belong in $group1 then i want it to print "both values belong in group1"
And if 1 value belongs in group1 and one value in group 2 then echo "1 value belongs in group1 and one value belongs in group2"
--edit
I forgot to mention that this output can change its not always this:
Array(
[0] => Social media campagne
[1] => Marketingcampagne
)
it can also be 3 products for example
It looks like what you want to do relates to set theory. You have a set of things (the return from the function) and you want to see if all the elements in that array are in another array (You want to check if the return array is a strict subset of the array you're checking against).
You can use array_intersect () to do this. This function takes at least 2 arrays as its arguments, and it returns an array of elements that exist in all the arrays passed to it.
If you have an array specifying all the values you want to check against and another array that may or may not be a subset of that array then you can use array_intersect to get a list of all the elements in both arrays. If the number of elements in the output match the number of elements in the array that you want to check then the array must be a strict subset of the array you're checking against.
The following code demonstrates the basic principle:
$set = [1, 2, 3, 4, 5, 6, 7, 8]; // Data you want to test against
$subset = [2, 4, 6, 8]; // Strict subset of $set
$notSubset = [2, 4, 6, 10]; // Not a strict subset of $set because it contains 10
var_dump (count ($subset) === count (array_intersect ($set, $subset))); // true
var_dump (count ($notSubset) === count (array_intersect ($set, $notSubset))); // false
NOTE: array_intersect is only really suitable when the contents of the arrays being compared are all primitive types, as PHP will cast them all to string while comparing them. If you're comparing arrays of arrays or arrays of object then you're better off using array_uintersect () and specifying how the elements are compared yourself with a callback function.
To apply the array_intersect() from #GordonM to your problem (it seems like there are only strings in the arrays).
You first get the number of values from your output, that are in group1
$inGroup1 = array_intersect($ouput, $group1);
$inGroup1Count = count($inGroup1); // you can rewrite these two lines into one,
// if the actual matches are of no interest
If this is the same count as $output, than it's group1. If not, check how many are in group2. I guess you can code that yourself. And then check if inGroup1Count and inGroup2Count are both > 0. Than it's in both groups.
For logic reason you should also check if all are in group 2. Or is that impossible?
With checking both groups you can create an output like
echo $inGroup1Count.' value(s) belongs in group1 and '.$inGroup2Count.' value(s) belongs in group2';

Sort array with objects on property of objects

I would like to sort an array of objects by a property of the specific object. This is my array with objects:
As you can see I have an array $all_studies with 2 objects. How can I now sort on the graduationYear property of the objects? So I would like to have an array with objects and the the order with object 2010 first, then 2014, ... (in this case the order is already correct but this won't always be the same ..).
This is what I've tried but with no succes:
$all_studies = usort($all_studies, "sort_objects_by_graduationyear");
function sort_objects_by_graduationyear($a, $b) {
if((int)$a->graduationYear == (int)$b->graduationYear){ return 0 ; }
return ($a->graduationYear < $b->graduationYear) ? -1 : 1;
}
But I just get true back. I've never used the usort function so I don't really know how to work with it. Can someone help me?
The function usort returns "true" on success. So, good news :).
If you want to check if the sort is done, you only have to check your $all_studies object after the usort.
$status = usort($all_studies, "sort_objects_by_graduationyear");
print_r($all_studies);
You were assigning the value of usort to $all_studies which'll be true and false thus you were not getting the value as desired. In fact you need to just sort the array and print that values and its all done
Try as
usort($all_studies, "sort_objects_by_graduationyear");
function sort_objects_by_graduationyear($a, $b) {
if((int)$a->graduationYear == (int)$b->graduationYear){ return 0 ; }
return ($a->graduationYear < $b->graduationYear) ? -1 : 1;
}
print_r($all_studies);
Return Values ΒΆ
Returns TRUE on success or FALSE on failure.
Check Docs

Sort an associative array by using another array's ordered key-value association

Finding the right title for this was next to impossible.
Imagine this scenario:
We have an array that contains certain product tags. The key is each tag's unique id and the value is its label:
Available Tags
Array (
[3] => Sweet
[4] => Sour
[5] => Bitter
[6] => Winter
[7] => Organic
)
We have another array which contains the tags that have been selected. The selection has a specific order which is defined by the key, while the value represents the id (of the actual tag we see in array #1).
Selected Tags in Specific Order
Array (
[10] => 4
[20] => 3
[30] => 7
)
My theoretical Approach
Certainly i could go about foreach-ing through the second array, collecting the appropriate values (that correspond to the first array's entries) in a new array. Then i could iterate over the first array and add all the values (to the new array) which are not yet present in the new array.
Quite honestly - that doesn't feel very professional. Unfortunately, i have no idea how to do this better.
Question
How can i neatly sort the first array (Available Tags) by using the chronology defined by the second array (Selected Tags)?
Note
I want to end up with all items from the first array. Not just the ones that are listed in the second one.
In case someone's curious: this is for multiple-selects which are sortable. Items which have been selected are sortable and must therefore appear in the right order. The other items order doesn't matter. My server-side data handler class gives me these two arrays as described, so that's what i got to work with.
Here's a solution that uses uksort(). Elements of the $tags array that are not present in the $order array are sorted to the end, and the relative order between them is undefined.
function my_sort($a, $b) {
global $order;
if(in_array($a, $order)) {
if(in_array($b, $order)) {
// Both $a and $b have an order
return array_search($a, $order) - array_search($b, $order);
}
else {
// Only $a has an order, so it goes before $b
return -1;
}
}
else if(in_array($b, $order)) {
// Only $b has an order, so it goes before $a
return 1;
}
else {
// Neither $a or $b has an order, so we don't care how they're sorted
return 0;
}
}
uksort($tags, 'my_sort');
I think you can just loop in your second array and build a new one using keys
$new = array();
foreach($array2 as $key => $val)
{
$new_array[] = $array1[$val];
}
Now the selected items are ordered in your $new_array
Sample

sorting arrays - Sorting an array from outside data

I am attempting to make an ordered array based on an unsorted array from an SQL database.
The data that is gotten from the database will look something like this:
Array (
//array ('name', position)
array ('george', 2),
array ('lenny' , 4),
array ('rabbit', 1),
array ('pet' , 3)
)
The idea is to sort the 'names' in an array where position is there place in the array.
What I would like to to end up being:
Array ( 'rabbit', 'george', 'pet', 'lenny' )
The current way I have attempted this is using split_array()
$result is the array from the database.
foreach ( $result as $res ){
$a = array(array($res['name'], $res['position']));
array_splice($finalArray, ($res['position'] - 1), 0, $a);
}
The issue is sometimes depending on the order the users are retrieved it will not sort it properly, is there a better way to do this, or is this good and I am doing it wrong?
Thanks.
Use uasort http://php.net/manual/en/function.uasort.php function where you can pass a user defined comparasion function like this:
$myArray = array(array('bill',3),array('joe',4),array('john',1));
/**
* #desc compare two arrays by the second element
* #param array $a (array to compare with an other)
* #param array $b (array to compare with an other)
* #return int 0|1|-1 equals|first is bigger|second is bigger
*/
function myCompare($a,$b){
if( $a[1] == $b[1] ){
return 0; //if the 2nd elements equals return 0
}
return ( $a[1] > $b[1] )?1:-1; //if the 2nd element of the 1st parameters is bigger returns 1 , else returns -1
}
Usage:
uasort( $myArray, 'myCompare' );
The uasort manipolates the original array in place.
Result:
var_dump($myArray);
array(
array('john',1),
array('bill',3),
array('joe',4)
);
Recommendation:
If you could edit the SQL query , better to short the results in the query with ORDER BY directive like this:
SELECT `name`,`position`
FROM `mytable` #your table name
WHERE 1 #or your conditions here
ORDER BY `position` ASC #ordering directive
This should run faster. And if use this, recommend to add index to position field.

Categories