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.
Related
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');
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.
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();
});
function foo($a)
{
$b = ...;
$c = ...;
return (both b and c);
}
and so I could get $b value to $first and $c value to $second
I know you can return more than 1 variable by return array($b,$c) but then it should be $var[0] and $var[1] and then I need to type $first = $var[0] and $second = $var[1] and so I'm creating more useless variables
So is it possible to do so without array?
Fundamentally, functions only have one return value. You could return a class with member variables first and second, or an associative array with keys "first" and "second", but you'll still only be returning a single object.*
Alternatively, you could references to $first and $second into your function:
function foo($a, &$b, &$c)
{
$b = ...;
$c = ...;
}
foo(42, $first, $second);
I'm not a big fan of this approach, though, because it's not immediately clear from the call-site that $first and $second are going to be modified.
* Note that if you return an array, you can always use the short-hand list($first,$second) = foo(42);.
No, it can't.
But you can still return an array from function, but use "list" to accept the result for convenient:
list ($first, $second) = foo ($a);
No, you cannot to that. Function returns only one result.
What you can do, if possible in you case, is pass a variable by reference.
function foo($a, &$b, &$c){
$b = ...;
$c = ...;
}
The following will make changes to $b and $c visible outside of the function scope.
The only alternative to avoid returning an array is to return an object, or serialized data, but you don't "win" something from that.
I have a variable $v that can be either single string or array of stringsand I have a code:
$a = array();
if (is_array($v)) {
$a = $v;
} else {
$a[] = $v;
}
How it can be done in more elegant way? (in other words, how to cast a variable to array)
You can cast a variable to an array by using:
$var = (array)$arr;
$a = (array) $v;
is the answer.
I would write your could snippet like this (short and you read it and know exactly what is happening):
$a = is_array($v) ? $v : array($v);
Alternatively you could use settype:
settype($a, "array");
For expliciting the variable type. It's exactly the same as what happens with a typecast behind the scenes. (More useful for group-wise typecasting e.g. in loops.)
As others have said, casting a scalar value to an array will produce a singleton array (i.e. an array with the scalar as its only element). However, as still others have pointed out, take care to only do this if you know the value is going to be a scalar and not a class instance.
From the PHP docs:
For any of the types integer, float, string, boolean and resource,
converting a value to an array results in an array with a single
element with index zero and the value of the scalar which was
converted. In other words, (array)$scalarValue is exactly the same as
array($scalarValue).
If an object is converted to an array, the result is an array whose
elements are the object's properties. The keys are the member variable
names, with a few notable exceptions: integer properties are
unaccessible; private variables have the class name prepended to the
variable name; protected variables have a '*' prepended to the
variable name. These prepended values have null bytes on either side.
If $v is a scalar (Boolean, String, Number) you can use:
a) $v = (array)$v;
If $v is an object, you have to use:
b) $v = is_array($v) ? $v : array($v);
Method (b) works in every case (with scalars too).
If you are looking to convert an object to a single count array you can use the follow code:
$list = array([0] => $obj);
The other provided answers won't work when trying to convert an object, it will simply convert the fields of that object into an associative array (unless that is what you are trying to do).
$var = (array)$arr;
Actually if you want to cast to an array and not have to worry about what you put into it, the answer is
$var = (is_object($var)) ? array($var) : (array) $var;
You can test this with the following code
function toArray($var) {
return (is_object($var)) ? array($var) : (array) $var;
}
$object = new stdClass;
$resource = fopen('php://stdout', 'w');
$closure = function () {};
$tests = array(
array(toArray(true), array(true), 'boolean true'),
array(toArray(false), array(false), 'boolean false'),
array(toArray(null), array(), 'null'),
array(toArray(1), array(1), 'positive integer'),
array(toArray(0), array(0), 'zero integer'),
array(toArray(-1), array(-1), 'negative integer'),
array(toArray(1.5), array(1.5), 'positive float'),
array(toArray(0.0), array(0.0), 'zero float'),
array(toArray(-1.5), array(-1.5), 'negative float'),
array(toArray(''), array(''), 'empty string'),
array(toArray('foo'), array('foo'), 'string'),
array(toArray(array()), array(), 'array'),
array(toArray($object), array($object), 'object'),
array(toArray($resource), array($resource), 'resource'),
array(toArray($closure), array($closure), 'closure'),
);
foreach ($tests as $test) {
ob_start();
var_dump($test[0]);
$a = ob_get_clean();
ob_start();
var_dump($test[1]);
$b = ob_get_clean();
assert($a === $b, "{$test[2]} is not the same");
}