PHP Get differences in multidimensional array based on second value - php

I have two multidimensional arrays, each element in the array consists of 2 elements, the first is a string and the second is an integer. I want to get the difference between the two multidimensional arrays based on the second value if and only if the first elements are equal. I am using array_udiff such as below:
$arrdiff = array_udiff($arr1, $arr2, 'udiffCompare');
I implemented the function array_udiff such that if the first element is different to return them as equal since I don't want it to appear in the difference, and if the first element is equal then compare the second element and return accordingly, below is the function I implemented
function udiffCompare($a, $b) {
return strcmp($a[0], $b[0]) == 0 ? $ a[1] - $b[1] : 0;
}
However, even though I have two arrays with the same first element but a different second element, they are not returned in the result of array_udiff function.
Am I missing anything here? Any help is appreciated.

The problem is, you're looking for a difference within an intersection, using only a difference function.
Try computing the intersection based on the string value, and using the result to compute the difference based on the int value.
function sameString ($a, $b) {
return strcmp($a[0], $b[0]);
}
function differentInt($a, $b) {
return $a[1] - $b[1];
}
$diff = array_udiff(array_uintersect($arr1, $arr2, 'sameString'), $arr2, 'differentInt');

Related

PHP array multisort (version 5/7 differences)

I got a array like this:
$json = '{"Categorie1":[{"created":"2017-07-17 08:53:00","catid":"54"},{"created":"2017-05-23 10:15:00","catid":"54"},{"created":"2017-05-09 05:49:23","catid":"54"}],"Categorie2":[{"created":"2017-03-21 08:58:37","catid":"59"},{"created":"2016-12-23 12:48:00","catid":"59"},{"created":"2016-12-08 09:57:10","catid":"59"}],"Categorie3":[],"Categorie4":[{"created":"2017-08-02 07:15:07","catid":"70"},{"created":"2017-08-01 08:03:00","catid":"70"},{"created":"2017-07-31 09:25:00","catid":"70"}],"Categorie5":[{"created":"2017-07-26 14:09:00","catid":"74"},{"created":"2017-06-29 14:03:00","catid":"74"},{"created":"2017-06-28 06:35:35","catid":"74"}]}';
And I wrote a sorting function. Basically it checks which block (categorie) got the newest entry and brings that block to top, sort the blocks):
$array = json_decode($json, true);
function custom($a, $b) {
foreach($a as $k => $v) {
if (isset($b[$k])) {
return (strtotime($a[$k]["created"]) <= strtotime($b[$k]["created"]));
}
}
}
uasort($array, "custom");
When I print this with PHP 5 its perfect: Categorie4 is the first block". But with PHP 7 it doesn't.
print("<pre>");
print_r($array); // PHP 5 is as expected, php 7 is not
I know there where changes, but I can't figure out how to change my code.
Can you guys helping me change the code? The result should show categorie4 as first cat...
The callback function used by the user-defined array sorting functions must return an integer value that is <0 if $a < $b, 0 when $a == $b or >0 when $a > $b. Yours return a boolean that is converted to 1 or 0 and that doesn't reflect the correct order of $a and $b.
It's not clear from the question how should be sorted the empty entries (Categorie3) and I think their place is at the end.
Try this code for PHP 5:
uasort($array, function (array $a, array $b) {
if (empty($a)) { return +1; } // empty arrays go to the end of the list
if (empty($b)) { return -1; }
return strcmp($a[0]['created'], $b[0]['created']);
});
The date&time values of created use a format that can be sorted directly as strings, there is no need to convert them to timestamps.
In PHP 7 you can use the new <=> comparison operator that, in theory, should run faster than the strcmp() function while it produces the same result.
uasort($array, function (array $a, array $b) {
if (empty($a)) { return +1; } // empty arrays go to the end of the list
if (empty($b)) { return -1; }
return $b[0]['created'] <=> $a[0]['created'];
});

sort array by length and then alphabetically

I'm trying to make a way to sort words first by length, then alphabetically.
// from
$array = ["dog", "cat", "mouse", "elephant", "apple"];
// to
$array = ["cat", "dog", "apple", "mouse", "elephant"];
I've seen this answer, but it's in Java, and this answer, but it only deals with the sorting by length. I've tried sorting by length, using the code provided in the answer, and then sorting alphabetically, but then it sorts only alphabetically.
How can I sort it first by length, and then alphabetically?
You can put both of the conditions into a usort comparison function.
usort($array, function($a, $b) {
return strlen($a) - strlen($b) ?: strcmp($a, $b);
});
The general strategy for sorting by multiple conditions is to write comparison expressions for each of the conditions that returns the appropriate return type of the comparison function (an integer, positive, negative, or zero depending on the result of the comparison), and evaluate them in order of your desired sort order, e.g. first length, then alphabetical.
If an expression evaluates to zero, then the two items are equal in terms of that comparison, and the next expression should be evaluated. If not, then the value of that expression can be returned as the value of the comparison function.
The other answer here appears to be implying that this comparison function does not return an integer greater than, less than, or equal to zero. It does.
Note: I didn`t post my answer early,because #Don't Panic faster then me. However,I want to add some explanation to his answer ( hope, it will be useful for more understanding).
usort($array, function($a, $b) {
return strlen($a) - strlen($b) ?: strcmp($a, $b);
});
Ok. Function usort waits from custom comparison function next (from docs):
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.
Ok, rewrite #Don't Panic code to this view (accoding the condition above):
usort($array, function($a, $b) {
// SORT_ORDER_CONDITION_#1
// equals -> going to next by order sort-condition
// in our case "sorting alphabetically"
if (strlen($a) == strlen($b)){
// SORT_ORDER_CONDITION_#2
if (strcmp($a,$b)==0) // equals - last sort-condition? Return 0 ( in our case - yes)
return 0; //
return (strcmp($a,$b)) ? -1 : 1;
}else{
return (strlen($a) < strlen ($b) ) ? - 1 : 1;
}
});
"Common sort strategy" (abstract) with multi sort-conditions in order like (CON_1,CON_2... CON_N) :
usort($array, function(ITEM_1, ITEM_2) {
// SORT_ORDER_CONDITION_#1
if (COMPARING_1_EQUALS){
// SORT_ORDER_CONDITION_#2
if (COMPARING_2_EQUALS){ // If last con - return 0, else - going "deeper" ( to next in order)
//...
// SORT_ORDER_CONDITION_#N
if (COMPARING_N_EQUALS) // last -> equals -> return 0;
return 0;
return ( COMPARING_N_NOT_EQUALS) ? -1 : 1;
//...
}
return ( COMPARING_2_NOT_EQUALS) ? -1 : 1;
}else{
return ( COMPARING_1_NOT_EQUALS ) ? - 1 : 1;
}
});
In practise (from my exp), it's sorting unordered multidimensional-array by several conditions. You can use usort like above.
This is not as short as other methods, but I would argue that it's clearer, and can be easily extended to cover other use cases:
$f = function ($s1, $s2) {
$n = strlen($s1) <=> strlen($s2);
if ($n != 0) {
return $n;
}
return $s1 <=> $s2;
};
usort($array, $f);

php - check if two arrays have the same values

I would like to know which is the fastest way to check that two (possibly multidimensional) arrays contain the same values.
In other terms I would like to know if the (unordered) set of values of the first array is equal to the set of values of the second array
UPDATE:
1) cannot use == or ===. They check also for key equality.
2) cannot use array_diff. It doesn't work with multidimensional arrays
There's no simple way to do this for a nested array; you'll have to write your own recursive function. It looks like array_udiff isn't really suitable, either (due to requiring greater-than/less-than information from the comparison function);
This should do the trick:
function recursive_sort_array($a) {
if(!is_array($a))
return $a;
$a = array_map('recursive_sort_array', $a);
sort($a);
return array_values($a);
}
function arrays_different($a, $b) {
return recursive_sort_array($a) === recursive_sort_array($b);
}
What it does: The first function (recursive_sort_array) recursively sorts an array and returns only the values, tied to numeric indexes. Any arrays that have the same values (recursively) will be equal after this operation, and can then be compared with one of the equality operators.

Sort array of objects by date field

How can I re-arrange a array of objects like this:
[495] => stdClass Object
(
[date] => 2009-10-31 18:24:09
...
)
[582] => stdClass Object
(
[date] => 2010-2-11 12:01:42
...
)
...
by the date key, oldest first ?
usort($array, function($a, $b) {
return strtotime($a['date']) - strtotime($b['date']);
});
Or if you don't have PHP 5.3:
function cb($a, $b) {
return strtotime($a['date']) - strtotime($b['date']);
}
usort($array, 'cb');
Since the original question is about sorting arrays of stdClass() objects, here's the code which would work if $a and $b are objects:
usort($array, function($a, $b) {
return strtotime($a->date) - strtotime($b->date);
});
Or if you don't have PHP 5.3:
function cb($a, $b) {
return strtotime($a->date) - strtotime($b->date);
}
usort($array, 'cb');
I wanted to expand on arnaud576875's answer. I ran across this same issue, but with using DateTime objects. This is how I was able to accomplish the same thing.
usort($array, function($a, $b) {
return $a['date']->format('U') - $b['date']->format('U');
});
Because your month -- and possibly your day -- values are not zero-padded, you cannot instantly compare the dates as simple strings. You should use strtotime() to convert the dates to unix time integers -- these will be suitable for reliable comparisons.
Also, it seems important to not lose the associative relationship between the first level keys and their objects. To sort and retain the keys, call uasort(). In modern php, the spaceship operator is the go-to utility for making 3-way comparisons (returns -1, 0, or 1).
Code:
uasort(
$array,
function($a, $b) {
return strtotime($a->date) <=> strtotime($b->date);
}
);
Or in PHP7.4, there is arrow function syntax:
uasort(
$array,
fn($a, $b) => strtotime($a->date) <=> strtotime($b->date)
);
The only minor drawback with using function calls in u*sort()'s body is that it will do greater than n sets of function calls to break ties and otherwise determine the correct order. An alternative sorting technique that avoids these redundant function calls is array_multisort(). It can be fed a column of data which has had exactly n function calls performed -- this effectively makes it more efficient. However, this sorting function has its own caveat -- it will lose the numeric first level keys. This is probably not a tolerable loss for this case.
Code:
array_multisort(
array_map('strtotime', array_column($array, 'date')),
$array
);
Here is a demo of both techniques.
For anyone who is sorting date, time, or datetime values that can be naturally compared as basic strings (so-called Big-endian formats such as Y-m-d H:i:s, H:i, m/d, Y-m-d, etc., then see this answer for more efficient techniques.
I wanted to expand on arnaud576875 and Michael Irigoyen.
Same issue with object containing dateTime with Symphony.
I coudn't use $a['date'] because it was not an key array.
usort($verifications, function($a, $b) {
return $a->getDate()->format('U') - $b->getDate()->format('U');
});
This solved my problem
When working with DateTime objects as properties of your Entity, this worked for me:
$myArray = $entityRepository->findAll();
usort($myArray, function($a, $b) {
return $a->getDate()->getTimestamp() - $b->getDate()->getTimestamp();
});

array intersect for object array php

I want to know how to array_intersect for object array.
You can use array_uintersect in conjunction with spl_object_hash, see an example:
array_uintersect($a, $b, function($a, $b) {
return strcmp(spl_object_hash($a), spl_object_hash($b));
});
where '$a' and '$b' are arrays of some objects that you want to intersect.
nice toString function is already implemented and is called serialize ;) so
array_map(
'unserialize',
array_intersect(
array_map(
'serialize',
$obj1
),
array_map(
'serialize',
$obj2
)
)
);
will do the work, example mentioned higher don't work 'cause array_intersect work's only with strings as someone mentioned too
array_intersect() returns an array containing all the values of array1 that are present in all the arguments.
Then what mean present in this context (exacly this function), i found on php.net my answer:
Two elements are considered equal if
and only if (string) $elem1 ===
(string) $elem2. In words: when the
string representation is the
same.
Then you can't use it on array of objects if your objects not implements unique conversion to string.
Had a similar problem a few days ago, while these are answers are on the right path; I used them to work out the following:
From Artefacto's answer return $obj1 == $obj2 didn't really work, so I wrote a simple comparative function (basically gets the md5 of the serialised object and compares that):
function object_compare($obj1, $obj2){
$md5 = function($obj){
return md5(serialize($obj));
};
return strcmp($md5($obj1), $md5($obj2));
}
Then it’s jut a matter of calling array_uintersect with our comparative function to get the intersection:
# $array1 / $array2 are the array of objects we want to compare
return array_uintersect($array1, $array2, 'object_compare');
In my case, I had an unknown / dynamic array of objects, so I took it a step further so I don't have to declare array_uintersect($array1, $array2, ...) specifically - but just be able to pass in an array of arrays (of objects):
# $multiarray_of_objects is our array of arrays
$multiarray_of_objects[] = 'object_compare';
return call_user_func_array('array_uintersect', $multiarray_of_objects);
Just gotta remember to pass in the reference to our callback / comparative function as the last string in the array. Works like a charm!
The correct way to check whether two objects are equal is to use ==. Therefore:
array_uintersect($arr1, $arr2, function ($a1, $a2) { return $a1 == $a2; });
Just for completeness: Implement __toString() method in your object returning a unique value. For database entities this might be as easy as returning the fully qualified class name postfixed with the ID of the record. But it can also be arbitrarily complex by doing some hashing or even worse things.
In my opinion, it's the class's duty to serialize itself or create something unique to compare its objects by. Using anything outside of a class to serialize an object might result in strange behaviour (including comparing objects of different classes, which must never result in equality).
I use array_udiff to implement array_intersect for an object array.
function diff($a, $b) {
if($a === $b) {
return 0;
} else {
return 1;}
}
$array_1 = array('a', 'b', 'c');
$array_2 = array('c', 'd','e');
$array = array_udiff($array_1, array_udiff($array_1, $array_2, 'diff'),'diff');
var_dump($array);
return array(1) { [2]=> string(1) "c" }
You can have your own diff function for any scheme.
The correct solution would be:
array_uintersect($arr1, $arr2, function ($a1, $a2) { return $a1 != $a2; });
Note the != in the callback function as opposed to the answer from #Artefacto. Based on the documentation of array_uintersect, the callback function has to return 0 (false) if array items are equal.

Categories