I'm looking for an efficient way of comparing two arrays in PHP. I have a "before" and an "after" array, and I need to get arrays of specific changes. I did manage to get part of the code right (not sure about how effective it is), but I just can't seem to get the last comparison to work.
The first part of every element is essentially an ID, which stays the same even if the second element, essentially a Name, is changed - note Name1-Renamed for example. The ID is the same. Sometimes an element might be removed (see ID 1233, 'Name3-Deleted' is only in the 'before' array) or added (as in the case of ID 1230, 'Name4-New'). Also note that IDs, while unique, are NOT sorted in any particular order.
So, I would need to find the items that have been
- Added (available 'after', but not 'before')
- Removed (available 'before', but not 'after')
- Changed (available in both, as there is an ID match, but the Name has changed)
And I can't for the life of me find an effective way to get the Changed elements (preferably without ifs or extraneous loops).
Also, what do you think? Is array_udiff the fastest/best method for this particular task?
<?php
//'BEFORE' ARRAY
$arr1 = array( array(1231, 'Name1'), array(1232, 'Name2'), array(1233, 'Name3-Deleted') );
//'AFTER' ARRAY
$arr2 = array( array(1231, 'Name1-Renamed'), array(1232, 'Name2'), array(1230, 'Name4-New') );
//'ADDED' ARRAY
$arr3 = array_udiff($arr2, $arr1, create_function(
'$a,$b',
'return $a[0] - $b[0]; ')
);
//'REMOVED' ARRAY
$arr4 = array_udiff($arr1, $arr2, create_function(
'$a,$b',
'return $a[0] - $b[0]; ')
);
//'CHANGED' ARRAY. CAN'T GET THIS TO WORK PROPERLY. EXPECTED RESULT IS AN ARRAY FOR THE RENAMED ITEM.
$arr5 = array_udiff($arr2, $arr1, create_function(
'$a,$b',
'return (strcmp($a[1],$b[1]))*(strcmp($a[0],$b[0])); ')
);
print("Elements Added\n");
print_r($arr3);
print("Elements Removed\n");
print_r($arr4);
print("Elements Renamed\n");
print_r($arr5);
?>
So, that is pretty much it. Does anybody know how to fix this issue? Thanks in advance for all your help!
//'BEFORE' ARRAY
$arr1 = array( array(1231, 'Name1'), array(1232, 'Name2'), array(1233, 'Name3-Deleted') );
//'AFTER' ARRAY
$arr2 = array( array(1231, 'Name1-Renamed'), array(1232, 'Name2'), array(1230, 'Name4-New') );
// Kudos to AbraCadaver for the following:
$arr1 = array_column($arr1, 1, 0);
$arr2 = array_column($arr2, 1, 0);
$added = array_diff_key($arr2, $arr1);
$deleted = array_diff_key($arr1, $arr2);
$modified = array_diff_key(array_diff($arr2, $arr1), $deleted, $added);
Fill results into $added & $changed arrays instead of outputting directly, if you need them for later.
$copyArr2 = $arr2;
foreach ($arr1 as $subArr1) {
$hit = false;
foreach ($copyArr2 as $key=>$subArr2) {
if ($subArr1[0] == $subArr2[0]) {
$hit = true;
if ($subArr1[1] != $subArr2[1]) {
print("Element changed:\n ".print_r($subArr2, true));
}
unset($copyArr2[$key]);
}
}
if (!$hit)
print("Element removed:\n ".print_r($subArr1, true));
}
print("Elements added:\n");
print_r($copyArr2);
Output:
Element changed:
Array
(
[0] => 1231
[1] => Name1-Renamed
)
Element removed:
Array
(
[0] => 1233
[1] => Name3-Deleted
)
Elements added:
Array
(
[2] => Array
(
[0] => 1230
[1] => Name4-New
)
)
Update: Made small fixes.
Related
This question already has answers here:
Using a string path to set nested array data [duplicate]
(8 answers)
Closed 2 years ago.
I want to find a workaround for the following problem:
I have n vectors(unique), like the following: ("val1", "val2", "val3", ..., "valn" ).
Each vector's length is different.
I want to add any of those in a new array, but using the vector values(val1,val2,val3) elements as sub-elements recursively for the new array, taken from the main vector(val1 => val+1 => val+2 => val+3 => ... val+n => solution), and the last element of the vector is an integer or a string(not a sub-array/vector as the others), which will match with the last element of the new array, and it's new array's soluton/target.
The workaround solution I am applying right now is this:
Let's suppose the target(solution) is the end value of the array(an integer or string).
In this case I suppose to work on a vector with 4 elements, where the last one is the solution.
$vector = array("val1", "val2", "val3", "target");
$count = count($vector);
$new_array = array();
switch($count){
case 1:
....
case 4:
$new_array[$vector[0]][$vector[1]][$vector[2]] = $vector[3];
/*New array will be
$new_array = [
val1 =>
val2 =>
val3 => "target"
];
*/
break;
}
The vectors I am using are many and with different sizes, so the solution/target can be in the 1st element, second, third and so on, so I applied in my switch any cases from 0 to 5 for example, working as wrote above.
I think there could be a better solution, to loop inside a for(or better, a while) cycle
But I am currently having no ideas on how it should be, and I didn't find any workaround in the web.
Does anyone have a soluton for this?
Thanks in advance
You can build the resulting array starting from the most recent nested element:
$vector = array("val1", "val2", "val3", "val4", "val5", "target");
$new_array = array_pop($vector);
foreach(array_reverse($vector) as $val) {
$new_array = [$val => $new_array];
}
print_r($new_array);
Hi You can change your code like it:
<?php
$vector = array("val1", "val2", "val3", "val4", "val5", "target");
$count = count($vector);
$new_array = array();
$new_array[$vector[$count - 2]] = $vector[$count - 1];
for ($i=($count - 3); $i >= 0; $i--) {
$temp_array = array();
$temp_array[$vector[$i]] = $new_array;
$new_array = $temp_array;
}
print_r($new_array);
And the result will be like it:
Array
(
[val1] => Array
(
[val2] => Array
(
[val3] => Array
(
[val4] => Array
(
[val5] => target
)
)
)
)
)
I am new to using multidimensional arrays with php, I have tried to stay away from them because they confused me, but now the time has come that I put them to good use. I have been trying to understand how they work and I am just not getting it.
What I am trying to do is populate results based on a string compare function, once I find some match to an 'item name', I would like the first slot to contain the 'item name', then I would like to increment the priority slot by 1.
So when when I'm all done populating my array, it is going to have a variety of different company names, each with their respective priority...
I am having trouble understanding how to declare and manipulate the following array:
$matches = array(
'name'=>array('somename'),
'priority'=>array($priority_level++)
);
So, in what you have, your variable $matches will point to a keyed array, the 'name' element of that array will be an indexed array with 1 entry 'somename', there will be a 'priority' entry with a value which is an indexed array with one entry = $priority_level.
I think, instead what you probably want is something like:
$matches[] = array(name => 'somename', $priority => $priority_level++);
That way, $matches is an indexed array, where each index holds a keyed array, so you could address them as:
$matches[0]['name'] and $matches[0]['priority'], which is more logical for most people.
Multi-dimensional arrays are easy. All they are is an array, where the elements are other arrays.
So, you could have 2 separate arrays:
$name = array('somename');
$priority = array(1);
Or you can have an array that has these 2 arrays as elements:
$matches = array(
'name' => array('somename'),
'priority' => array(1)
);
So, using $matches['name'] would be the same as using $name, they are both arrays, just stored differently.
echo $name[0]; //'somename';
echo $matches['name'][0]; //'somename';
So, to add another name to the $matches array, you can do this:
$matches['name'][] = 'Another Name';
$matches['priority'][] = 2;
print_r($matches); would output:
Array
(
[name] => Array
(
[0] => somename
[1] => Another Name
)
[priority] => Array
(
[0] => 1
[1] => 2
)
)
In this case, could this be also a solution with a single dimensional array?
$matches = array(
'company_1' => 0,
'company_2' => 0,
);
if (isset($matches['company_1'])) {
++$matches['company_1'];
} else {
$matches['company_1'] = 1;
}
It looks up whether the name is already in the list. If not, it sets an array_key for this value. If it finds an already existing value, it just raises the "priority".
In my opinion, an easier structure to work with would be something more like this one:
$matches = array(
array( 'name' => 'somename', 'priority' => $priority_level_for_this_match ),
array( 'name' => 'someothername', 'priority' => $priority_level_for_that_match )
)
To fill this array, start by making an empty one:
$matches = array();
Then, find all of your matches.
$match = array( 'name' => 'somename', 'priority' => $some_priority );
To add that array to your matches, just slap it on the end:
$matches[] = $match;
Once it's filled, you can easily iterate over it:
foreach($matches as $k => $v) {
// The value in this case is also an array, and can be indexed as such
echo( $v['name'] . ': ' . $v['priority'] . '<br>' );
}
You can also sort the matched arrays according to the priority:
function cmp($a, $b) {
if($a['priority'] == $b['priority'])
return 0;
return ($a['priority'] < $b['priority']) ? -1 : 1;
}
usort($matches, 'cmp');
(Sourced from this answer)
$matches['name'][0] --> 'somename'
$matches['priority'][0] ---> the incremented $priority_level value
Like David said in the comments on the question, it sounds like you're not using the right tool for the job. Try:
$priorities = array();
foreach($companies as $company) {
if (!isset($priorities[$company])) { $priorities[$company] = 0; }
$priorities[$company]++;
}
Then you can access the priorities by checking $priorities['SomeCompanyName'];.
Say I've got an array
[0]=>test
[2]=>example.
I want o/p as
[0]=>test
[1]=>example
In my first array
[1]=>NULL
I've tried to remove this and reorder so, I used array_filter() to remove the null value.
Now, how do I reorder the array?
If I understand what you need, I think array_values should help (it will return only the values in the array, reindexed from 0):
print_r( array_values($arr) );
Here's an example of this: http://codepad.org/q7dVqyVY
You might want to use merge:
$newArray = array_merge(array(),$oldArray);
$Array = array('0'=>'test,', '2'=>'example');
ksort($Array);
$ArrayTMP = array_values($Array);
or
$Array = array('0'=>'test,', '2'=>'example');
ksort($Array);
$ArrayTMP = array_merge ($Array,array());
Credit goes to: http://www.codingforums.com/archive/index.php/t-17794.html.
<?php
$a = array(0 => 1, 1 => null, 2 => 3, 3 => 0);
$r = array_values(
array_filter($a, function ($elem)
{
if ( ! is_null($elem))
return true;
})
);
// output:
array (
0 => 1,
1 => 3,
2 => 0,
)
?>
NOTE: I am using an anonymous callback function for array_filter. Anonymous callback only works in php 5.3+ and is the appropriate in this case (IMHO). For previous versions of php just define it as normal.
There is many - good and less good - ways to check associative arrays, but how would you check a "fully associative" array?
$john = array('name' => 'john', , 8 => 'eight', 'children' => array('fred', 'jane'));
$mary1 = array('name' => 'mary', 0 => 'zero', 'children' => array('jane'));
$mary2 = array('name' => 'mary', 'zero', 'children' => array('jane'));
Here $john is fully associative, $mary1 and $mary2 are not.
To make it short, you can't because every array is implemented the same way. From the docs:
An array in PHP is actually an ordered map. A map is a type that associates values to keys.
If have no insight in the implementation, but I'm pretty sure that array(1,2,3) is just shorthand for array(0=>1, 1=>2, 2=>3), i.e. in the end it is exactly the same. There is nothing with which you could distinguish that.
You could only assume that arrays created via array(value, value,...) have an index with 0 and the others have not. But you have already seen that this must not always be the case.
And every attempt to detect an "associative" array would fail at some point.
The actual question is: Why do you need this?
Is this what you're looking for?
<?php
function is_assoc( $array ) {
if( !is_array( $array ) || array_keys( $array ) == range( 0, count( $array ) - 1 ) ) {
return( false );
}
foreach( $array as $value ) {
if( is_array( $value ) && !is_assoc( $value ) ) {
return( false );
}
}
return( true );
}
?>
The detection depends on your definition of associative. This function checks for the associative that means arrays that don't have sequential numeric keys. Some may say that associative is anything where the key was implicitly set instead of calculated by php. Others may even define all PHP arrays as associative (in which case is_array() would have sufficed). Again, it all depends, but this is the function I use in my projects. Hopefully, it's good enough for you.
How can I get the last key of an array?
A solution would be to use a combination of end and key (quoting) :
end() advances array 's internal pointer to the last element, and returns its value.
key() returns the index element of the current array position.
So, a portion of code such as this one should do the trick :
$array = array(
'first' => 123,
'second' => 456,
'last' => 789,
);
end($array); // move the internal pointer to the end of the array
$key = key($array); // fetches the key of the element pointed to by the internal pointer
var_dump($key);
Will output :
string 'last' (length=4)
i.e. the key of the last element of my array.
After this has been done the array's internal pointer will be at the end of the array. As pointed out in the comments, you may want to run reset() on the array to bring the pointer back to the beginning of the array.
Although end() seems to be the easiest, it's not the fastest. The faster, and much stronger alternative is array_slice():
$lastKey = key(array_slice($array, -1, 1, true));
As the tests say, on an array with 500000 elements, it is almost 7x faster!
Since PHP 7.3 (2018) there is (finally) function for this:
http://php.net/manual/en/function.array-key-last.php
$array = ['apple'=>10,'grape'=>15,'orange'=>20];
echo array_key_last ( $array )
will output
orange
I prefer
end(array_keys($myarr))
Just use : echo $array[count($array) - 1];
Dont know if this is going to be faster or not, but it seems easier to do it this way, and you avoid the error by not passing in a function to end()...
it just needed a variable... not a big deal to write one more line of code, then unset it if you needed to.
$array = array(
'first' => 123,
'second' => 456,
'last' => 789,
);
$keys = array_keys($array);
$last = end($keys);
As of PHP7.3 you can directly access the last key in (the outer level of) an array with array_key_last()
The definitively puts much of the debate on this page to bed. It is hands-down the best performer, suffers no side effects, and is a direct, intuitive, single-call technique to deliver exactly what this question seeks.
A rough benchmark as proof: https://3v4l.org/hO1Yf
array_slice() + key(): 1.4
end() + key(): 13.7
array_key_last(): 0.00015
*test array contains 500000 elements, microtime repeated 100x then averaged then multiplied by 1000 to avoid scientific notation. Credit to #MAChitgarha for the initial benchmark commented under #TadejMagajna's answer.
This means you can retrieve the value of the final key without:
moving the array pointer (which requires two lines of code) or
sorting, reversing, popping, counting, indexing an array of keys, or any other tomfoolery
This function was long overdue and a welcome addition to the array function tool belt that improves performance, avoids unwanted side-effects, and enables clean/direct/intuitive code.
Here is a demo:
$array = ["a" => "one", "b" => "two", "c" => "three"];
if (!function_exists('array_key_last')) {
echo "please upgrade to php7.3";
} else {
echo "First Key: " , key($array) , "\n";
echo "Last Key: " , array_key_last($array) , "\n";
next($array); // move array pointer to second element
echo "Second Key: " , key($array) , "\n";
echo "Still Last Key: " , array_key_last($array);
}
Output:
First Key: a
Last Key: c // <-- unaffected by the pointer position, NICE!
Second Key: b
Last Key: c // <-- unaffected by the pointer position, NICE!
Some notes:
array_key_last() is the sibling function of array_key_first().
Both of these functions are "pointer-ignorant".
Both functions return null if the array is empty.
Discarded sibling functions (array_value_first() & array_value_last()) also would have offered the pointer-ignorant access to bookend elements, but they evidently failed to garner sufficient votes to come to life.
Here are some relevant pages discussing the new features:
https://laravel-news.com/outer-array-functions-php-7-3
https://kinsta.com/blog/php-7-3/#array-key-first-last
https://wiki.php.net/rfc/array_key_first_last
p.s. If anyone is weighing up some of the other techniques, you may refer to this small collection of comparisons: (Demo)
Duration of array_slice() + key(): 0.35353660583496
Duration of end() + key(): 6.7495584487915
Duration of array_key_last(): 0.00025749206542969
Duration of array_keys() + end(): 7.6123380661011
Duration of array_reverse() + key(): 6.7875385284424
Duration of array_slice() + foreach(): 0.28870105743408
As of PHP >= 7.3 array_key_last() is the best way to get the last key of any of an array. Using combination of end(), key() and reset() just to get last key of an array is outrageous.
$array = array("one" => bird, "two" => "fish", 3 => "elephant");
$key = array_key_last($array);
var_dump($key) //output 3
compare that to
end($array)
$key = key($array)
var_dump($key) //output 3
reset($array)
You must reset array for the pointer to be at the beginning if you are using combination of end() and key()
Try using array_pop and array_keys function as follows:
<?php
$array = array(
'one' => 1,
'two' => 2,
'three' => 3
);
echo array_pop(array_keys($array)); // prints three
?>
It is strange, but why this topic is not have this answer:
$lastKey = array_keys($array)[count($array)-1];
I would also like to offer an alternative solution to this problem.
Assuming all your keys are numeric without any gaps,
my preferred method is to count the array then minus 1 from that value (to account for the fact that array keys start at 0.
$array = array(0=>'dog', 1=>'cat');
$lastKey = count($array)-1;
$lastKeyValue = $array[$lastKey];
var_dump($lastKey);
print_r($lastKeyValue);
This would give you:
int(1)
cat
You can use this:
$array = array("one" => "apple", "two" => "orange", "three" => "pear");
end($array);
echo key($array);
Another Solution is to create a function and use it:
function endKey($array){
end($array);
return key($array);
}
$array = array("one" => "apple", "two" => "orange", "three" => "pear");
echo endKey($array);
$arr = array('key1'=>'value1','key2'=>'value2','key3'=>'value3');
list($last_key) = each(array_reverse($arr));
print $last_key;
// key3
I just took the helper-function from Xander and improved it with the answers before:
function last($array){
$keys = array_keys($array);
return end($keys);
}
$arr = array("one" => "apple", "two" => "orange", "three" => "pear");
echo last($arr);
echo $arr(last($arr));
$array = array(
'something' => array(1,2,3),
'somethingelse' => array(1,2,3,4)
);
$last_value = end($array);
$last_key = key($array); // 'somethingelse'
This works because PHP moves it's array pointer internally for $array
The best possible solution that can be also used used inline:
end($arr) && false ?: key($arr)
This solution is only expression/statement and provides good is not the best possible performance.
Inlined example usage:
$obj->setValue(
end($arr) && false ?: key($arr) // last $arr key
);
UPDATE: In PHP 7.3+: use (of course) the newly added array_key_last() method.
Try this one with array_reverse().
$arr = array(
'first' => 01,
'second' => 10,
'third' => 20,
);
$key = key(array_reverse($arr));
var_dump($key);
Try this to preserve compatibility with older versions of PHP:
$array_keys = array_keys( $array );
$last_item_key = array_pop( $array_keys );