It seems that a large, complicated codebase depends on the order arsort produces. Before I dive in to discern what's actually happening in like 50 classes -- is there a simple way to shuffle items with equal values?
In other words, if the input is
['foo' => 3, 'bar' => 3, 'baz' => 3, 'this' => 2, 'that' => 2]
I'd like to get
['baz' => 3, 'bar' => 3, 'foo' => 3, 'this' => 2, 'that' => 2]
one run maybe, then
['baz' => 3, 'bar' => 3, 'foo' => 3, 'that' => 2, 'this' => 2]
on another random run.
How about something like this? (Untested)
Worst Case complexity: O(k)
Note: Written for algorithmic clarity and not PHP details...
function shuffleInput( $data ) {
// Separate into sets.
$sets = [];
foreach ( $data as $k => $v ) {
$sets[$v][] = $k;
}
// Shuffle & Join.
$data = [];
foreach ( $sets as $v => &$set ) {
shuffle( $set );
foreach( $set as $k ) {
$data[$k] = $v;
}
}
return $data;
}
Depending on the size of your input, it might be a better idea to unset every element in $data in the first loop instead of just creating a new array. This applies if data is very large and memory is precious to you - as well as reducing any sudden spikes & dips in memory usage.
Also, if you're going to continously shuffle the same $data around you might want to separate out the making of $sets to some other place or at least allow the developer to pass/get it as a side effect.
If you do not want to deal with shuffle, but rather prefer to check all permutations of the array, then you can do something like this:
$arr = array('foo' => 3, 'bar' => 3, 'baz' => 3, 'this' => 2, 'that' => 2);
$keys = array_keys($arr);
$indexes = range(0, count($arr) - 1);
pc_permute($indexes, $perms);
var_dump($perms);
function pc_permute($items, &$ret = array(), $perms = array( )) {
if (empty($items)) {
$ret[] = $perms;
} else {
for ($i = count($items) - 1; $i >= 0; --$i) {
$newitems = $items;
$newperms = $perms;
list($foo) = array_splice($newitems, $i, 1);
array_unshift($newperms, $foo);
pc_permute($newitems, $ret, $newperms);
}
}
}
Array $perms will give all permutations of the indexes, key name by index you can get from $keys and value by key or index (use array_slice) from $arr :)
ps: but you should understand - more elements you have in the original array, more permutations you will find. if there are n elements then there will be n! permutations. for n = 5 there are 120 permutations.
Related
if ($item['id_piatto'] === $riga['id_piatto'] && $item['id_portata'] === $riga['id_portata'] && $item['id_dieta'] === $riga['id_dieta']) {
$item['quantita'] += $riga['quantita'];
}
Is there a more compact way to compare multiple values of two arrays instead of the one in the code above?
In order to compare several values of two arrays (Not the whole array!) with given keys, such a function is useful:
function array_cmp_keys(array $arr1, array $arr2, array $keys){
foreach($keys as $key){
if($arr1[$key] !== $arr2[$key]) return false;
}
return true;
}
Testdata:
$item = ['id_piatto' => 1, 'id_portata' => 2, 'id_dieta' => 3, 'xx' => 4];
$riga = ['id_piatto' => 1, 'id_portata' => 2, 'id_dieta' => 3, 'xx' => 5];
How to use:
$keys = ['id_piatto', 'id_portata', 'id_dieta'];
if(array_cmp_keys($item,$riga,$keys)) {
echo "Equal";
}
else {
echo "not Equal";
}
"Equal" is output for this test data
As the top comment points out, your original solution is just fine especially if you are only dealing with a small number of array elements. You only have 3 lines of code and the memory footprint is tiny. Go with your own solution unless you have a good reason not to.
If you have a large number of array elements to compare and a limited number of elements to exclude, you may wish to automate things a bit. Note that this has a memory hit because you're duplicating the array.
<?php
// Original arrays
$first = ['name' => 'tony', 'address' => '123 Main St.', 'position' => 'clerk', 'amount' => 15];
$second = ['name' => 'tony', 'address' => '123 Main St.', 'position' => 'clerk', 'amount' => 23];
// Keys that you want to exclude from the comparison.
$excludeInComparison = ['amount'];
// Duplicate the arrays but exclude these keys for the comparison.
$firstFiltered = array_diff_key($first, array_flip($excludeInComparison));
$secondFiltered = array_diff_key($second, array_flip($excludeInComparison));
// Compare the filtered arrays
if ($firstFiltered == $secondFiltered) {
$first['amount'] += $second['amount'];
}
You can use PHP's advanced array destructuring syntax, available from PHP 7.1:
['id_piatto' => $a[], 'id_portata' => $a[], 'id_dieta' => $a[]] = $item;
['id_piatto' => $b[], 'id_portata' => $b[], 'id_dieta' => $b[]] = $riga;
$item['quantita'] += ($a === $b) ? $riga['quantita'] : 0;
I think this looks nice and readable, but it really shines when the input arrays have different keys and/or nested structure, I'll give you an example:
$one = [
'foo' => 1,
'bar' => [1,2,3],
'baz' => ['baz_id' => 4]
];
$two = [
'foo' => 1,
'bar' => [5,5,3],
'boo_id' => 4
];
['foo' => $a[], 'bar' => [2 => $a[]], 'baz' => ['baz_id' => $a[]]] = $one;
['foo' => $b[], 'bar' => [2 => $b[]], 'boo_id' => $b[]] = $two;
var_dump($a === $b); // true
Note how I used a numeric index to access the third value in the 'bar' sub-array. Finally, this is much more flexible than the custom function approach in some of the other answers. For detailed info, Check out this blog post about array destructuring in PHP.
Addendum
If it's all about nice looking code and readability, is this really better than the standard approach (maybe structured with some linebreaks)?
if(
$one['foo'] === $two['foo']
&& $one['bar'][2] === $two['bar'][2]
&& $one['baz']['baz_id'] === $two['boo_id']
) {
// Do something...
}
Apart from the stylistic viewpoint, the standard approach has still two big advantages:
One could easily use an individual comparison operator for each expression
One could implement more advanced boolean logic
One the other hand, the destructuring approach will generate 2 "normalized" subsets of your input arrays on the fly, which could be useful later...
if($a === $b) {
// Do something
} else {
return [
'error_msg' => "Inputs don't match",
'context' => [
'expected' => $a,
'actual' => $b
]
];
}
My array is :
$array= array(4,3,4,3,1,2,1);
And I'd like to output it like below:
Output = 2
(As 2 is present only once)
This is what I've tried:
$array = array(4, 3, 4, 3, 1, 2, 1);
$array1 = array(4, 3, 4, 3, 1, 2, 1);
$array_diff = array_diff($array, $array1);
*Read last section of this an for the most stable technique the avoids fringe case issues -- it is also the most verbose.
One-liner with no loops: (Demo)
var_export(array_keys(array_intersect(array_count_values($array),[1])));
The breakdown:
array_keys( // return the remaining keys from array_count_values
array_intersect( // filter the first array by second
array_count_values($array), // count number of occurrences of each value
[1] // identify the number of occurrences to keep
)
)
if you (or any future reader) wants to keep more values, replace the second parameter/array in array_intersect().
for instance:
you want to keep 1,2,and 3: array(1,2,3) or [1,2,3]
p.s. For the record, you can use array_filter() with a custom function to omit all non-1 count values, but I have used array_intersect() because the syntax is more brief and IMO easier to read.
p.s. thought I'd revisit and include a PHP7.4 technique and compare against other function-based techniques...
Code: (Demo)
$numbers = [4, 3, 4, 3, 1, 2, 1];
var_export(
array_keys(
array_intersect(
array_count_values($numbers),
[1]
)
)
);
echo "\n---\n";
var_export(
array_keys(
array_filter(
array_count_values($numbers),
function($count) {
return $count === 1;
}
)
)
);
echo "\n---\n";
// PHP7.4+
var_export(
array_keys(
array_filter(
array_count_values($numbers),
fn($count) => $count === 1
)
)
);
*For similar tasks which have values which are not guaranteed to be integers, array_count_values() will complain with "Warning: array_count_values(): Can only count string and integer values".
Even a classic loop that uses values as first level keys like #LF00's answer will suffer potential side effects due to floats and numeric values being cast as integers automatically.
This means that a more general-use solution would be: (Demo)
$result = [];
foreach ($array as $index => $value) {
foreach ($array as $i => $v) {
if ($value === $v && $index !== $i) {
continue 2; // duplicate found, stop checking this value; do not retain
}
}
$result[] = $value;
}
var_export($result);
You could use the array_count_values() php function.
For example:
$numbers = [4, 3, 4, 3, 1, 2, 1];
// build count array as key = number and value = count
$counter_numbers = array_count_values($numbers);
print_r($counter_numbers);
Output :
Array
(
[4] => 2
[3] => 2
[1] => 2
[2] => 1
)
Then loop through the new array to get non-repeated values :
$unique_numbers = [];
foreach ($counter_numbers as $number => $count) {
if ($count === 1) {
$unique_numbers[] = $number;
}
}
print_r($unique_numbers);
Output :
Array
(
[0] => 2
)
You can do it like this: Count the occurrences of each element, then filter out the occurrences bigger than 1.
$array = [4, 3, 4, 3, 1, 2, 1];
foreach ($array as $v)
{
$arr[$v][] = 1; // doesn't matter if 1 is set to a different value
}
foreach($arr as $k => $v)
{
if (count($v) == 1) {
$o[] = $k;
}
}
print_r($o);
result:
Array
(
[0] => 2
)
If in your scenario there will be only one unique value you could use:
$array= array(4,3,4,3,1,2,1);
$singleValue = array_search(1, array_count_values($array));
var_dump($singleValue) // Outputs: 2
Basically I need to take two arrays, merge them with unique values and sum one of columns. It makes more sense when written out below:
$a = [
['ID' => 1, 'Count' => 2],
];
$b = [
['ID' => 1, 'Count' => 4],
['ID' => 2, 'Count' => 3]
];
and I need the final product to be:
$a_plus_b = [
['ID' => 1, 'Count' => 6],
['ID' => 2, 'Count' => 3]
];
I have been playing with different variations of array_merge() and array_unique(), but I can't find an efficient way to do what I need. I know I can always do nested loops, but I was hoping for something easier. Any ideas?
This should do the trick
Note: This solution requires PHP >= 5.3. There is a PHP < 5.3 solution below.
$input = array($a, $b);
// add as many result arrays to $input as you want; e.g.,
// $input = array($a, $b, $c, $d);
$output = array_count_values(
call_user_func_array(
'array_merge',
array_map(
function($arr) {
return array_fill(0, $arr['Count'], $arr['ID']);
},
call_user_func_array(
'array_merge',
$input
)
)
)
);
print_r($output);
Output
Array
(
[1] => 6
[2] => 3
)
Note the array keys above are ID values. The array values are Count values.
If you're running PHP < 5.2 you won't be able to use the inline closure with array_fill. You have to define it as a separate function.
$input = array($a, $b);
function _fill($arr) {
return array_fill(0, $arr['Count'], $arr['ID']);
}
$output = array_count_values(
call_user_func_array(
'array_merge',
array_map(
'_fill',
call_user_func_array(
'array_merge',
$input
)
)
)
);
print_r($output);
From here, converting the output to your desired format is a trivial task.
Please don't over-engineer such a basic task. Iterate both array with a single loop and assign temporary keys using ID values. If encountering a respective ID key more than once, just add the new Count value to the stored value.
Code: (Demo)
$result = [];
foreach (array_merge($a, $b) as $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
}
var_export(array_values($result));
Output:
array (
0 =>
array (
'ID' => 1,
'Count' => 6,
),
1 =>
array (
'ID' => 2,
'Count' => 3,
),
)
Functional programming can be used as well to achieve the same result -- array_reduce() is ideal since the number of elements in the output will be equal to or less than the number of elements in the input data.
Code: (Demo)
var_export(
array_values(
array_reduce(
array_merge($a, $b),
function ($result, $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
return $result;
},
[]
)
)
);
If the ID values in the first array are guaranteed to be unique, you can avoid the array_merge() call by porting the $a array to the result array and assigning temporary keys using the ID values. (Demo)
$result = array_column($a, null, 'ID');
foreach ($b as $row) {
if (!isset($result[$row['ID']])) {
$result[$row['ID']] = $row;
} else {
$result[$row['ID']]['Count'] += $row['Count'];
}
}
var_export(array_values($result));
I have an array with keys and values. Each value is an integer. I have an other array with the same keys. How can I subtract all of the values for the matching keys? Also there might be keys that do not occur in the second array but both arrays have the same length. If there is a key in array 2 that is not present in array 1 its value should be unchanged. If there is a key in the first array that is not in the second it should be thrown away. How do I do it? Is there any built-in function for this?
If I would write a script it would be some kind of for loop like this:
$arr1 = array('a' => 1, 'b' => 3, 'c' => 10);
$arr2 = array('a' => 2, 'b' => 1, 'c' => 5);
$ret = array();
foreach ($arr1 as $key => $value) {
$ret[$key] = $arr2[$key] - $arr1[$key];
}
print_r($ret);
/*
should be: array('a' => 1, 'b' => -2, 'c' => -5)
*/
I did not add the occasion here a key is in one array and not in the other.
You could avoid the foreach using array functions if you were so inclined.
The closure provided to array_mapdocs below will subtract each $arr1 value from each corresponding $arr2. Unfortunately array_map won't preserve your keys when using more than one input array, so we use array_combinedocs to merge the subtracted results back into an array with the original keys:
$arr1 = array('a' => 1, 'b' => 3, 'c' => 10);
$arr2 = array('a' => 2, 'b' => 1, 'c' => 5);
$subtracted = array_map(function ($x, $y) { return $y-$x; } , $arr1, $arr2);
$result = array_combine(array_keys($arr1), $subtracted);
var_dump($result);
UPDATE
I was interested in how the array functions approach compared to a simple foreach, so I benchmarked both using Xdebug. Here's the test code:
$arr1 = array('a' => 1, 'b' => 3, 'c' => 10);
$arr2 = array('a' => 2, 'b' => 1, 'c' => 5);
function arrayFunc($arr1, $arr2) {
$subtracted = array_map(function ($x, $y) { return $y-$x; } , $arr1, $arr2);
$result = array_combine(array_keys($arr1), $subtracted);
}
function foreachFunc($arr1, $arr2) {
$ret = array();
foreach ($arr1 as $key => $value) {
$ret[$key] = $arr2[$key] - $arr1[$key];
}
}
for ($i=0;$i<10000;$i++) { arrayFunc($arr1, $arr2); }
for ($i=0;$i<10000;$i++) { foreachFunc($arr1, $arr2); }
As it turns out, using the foreach loop is an order of magnitude faster than accomplishing the same task using array functions. As you can see from the below KCachegrind callee image, the array function method required nearly 80% of the processing time in the above code, while the foreach function required less than 5%.
The lesson here: sometimes the more semantic array functions (surprisingly?) can be inferior performance-wise to a good old fashioned loop in PHP. Of course, you should always choose the option that is more readable/semantic; micro-optimizations like this aren't justified if they make the code more difficult to understand six months down the road.
foreach ($arr2 as $key => $value) {
if(array_key_exists($key, $arr1) && array_key_exists($key, $arr2))
$ret[$key] = $arr2[$key] - $arr1[$key];
}
PHP does not offer vectorized mathematical operations. I would stick with your current approach of using a loop.
First, I would get the set of array keys for each array. (See the array_keys function). Then, intersect them. Now you will have the keys common to each array. (Take a look at the array_intersect function). Finally, iterate. It's a readable and simple approach.
Lastly, you could take a look at a library, such as Math_Vector: http://pear.php.net/package/Math_Vector
$arr[] = array(...,'id'=1,'prev'=>2,'next'=>null);
$arr[] = array(...,'id'=2,'prev'=>3..,'next'=>1);
$arr[] = array(...,'id'=3,'prev'=>4,'next'=>2);
..
The order of each record can be arbitary.
How to sort this kind of array so that record with prev's value null is first,and the one with null next is last?
An array is not a container for linked list. A linked list is a list with linked objects, not a list with objects that have relationships. Basically, what you've got is the worst of the two containers. I would try to transform that structure into some other data container; a real linked list never needs to be sorted the way you need to sort your data.
The good way would involve something like that. I'll leave to you the way to insert objects in the middle of the list, it's not that hard.
<?php
class LinkedObject
{
var $value;
var $prev;
var $next;
public function __construct($value, $prev = null, $next = null)
{
$this->value = $value;
$this->prev = $prev;
$this->next = $next;
}
public function append(LinkedObject $insertee)
{
$link = $this;
while($link->next != null)
$link = $link->next;
$link->next = $insertee;
$insertee->prev = $link;
}
public function __toString()
{
$str = $this->value;
if($this->next != null)
{
$str .= " » ";
$str .= $this->next;
}
return $str;
}
}
$head = new LinkedObject("foo");
$head->append(new LinkedObject("bar"));
$head->append(new LinkedObject("baz"));
echo $head . "\n"; // gives "foo » bar » baz"
?>
But, if for some occult reason you really, really need them in an array, here is what you would need:
<?php
function find_row($array, $id)
{
foreach($array as $current_row)
{
if($current_row['id'] === $id)
return $current_row;
}
return null;
}
function what_the_heck_sort($array)
{
$start_record = $array[0];
$working_record = $array[0];
$result = array($working_record);
while($working_record['prev'] !== null)
{
$working_record = find_row($array, $working_record['prev']);
array_unshift($result, $working_record);
}
$working_record = $start_record;
while($working_record['next'] !== null)
{
$working_record = find_row($array, $working_record['next']);
array_push($result, $working_record);
}
return $result;
}
// the test code
$test = array(
array("foo 01", 'id' => 0, 'prev' => null, 'next' => 1),
array("foo 02", 'id' => 1, 'prev' => 0, 'next' => 2),
array("foo 03", 'id' => 2, 'prev' => 1, 'next' => 3),
array("foo 04", 'id' => 3, 'prev' => 2, 'next' => 4),
array("foo 05", 'id' => 4, 'prev' => 3, 'next' => 5),
array("foo 06", 'id' => 5, 'prev' => 4, 'next' => 6),
array("foo 07", 'id' => 6, 'prev' => 5, 'next' => 7),
array("foo 08", 'id' => 7, 'prev' => 6, 'next' => 8),
array("foo 09", 'id' => 8, 'prev' => 7, 'next' => 9),
array("foo 10", 'id' => 9, 'prev' => 8, 'next' => null));
shuffle($test);
print_r(what_the_heck_sort($test));
?>
But really, do yourself a favor, and do a real linked list, using objects and not arrays. The sorting method above is, in my opinion, quite decent knowing the constraints, but it's ridiculously slow because it needs to look up the array for each id.
Hmm, so you want to get your array into something like:
$array[] = array('id'=>1324, 'prev'=>null, 'next'=>15834);
$array[] = array('id'=>15834, 'prev'=>1324, 'next'=>1023);
$array[] = array('id'=>1023, 'prev'=>15834, 'next'=>12482);
$array[] = array('id'=>12482, 'prev'=>1023, 'next'=>null);
no matter what order they started in? Well, that won't be a basic sort pattern, so I'd go with something like:
// Find the first entry
foreach($arr as $index => $row) {
if ($row['prev'] == null) {
// This is the first row
$cur_row = $row;
break; // Jump out of the foreach loop
}
}
$sorted = array();
$sorted[] = $cur_row;
while ($cur_row['next'] != null) {
// Find the next row
foreach($arr as $index => $row) {
if ($row['id'] = $cur_row['next']) {
// This is the next row
$sorted[] = $row;
$cur_row = $row;
break; // Jump out of the foreach loop
}
}
}
print_r($sorted); // $sorted now has your sorted array
I would do first a round with putting the id's as array keys. At the same moment recording the first element.
$newArray = array():
$firstElement = null;
foreach( $array as $row ) {
$newArray[$row['id']] = $row;
if( $row['prev'] == null ) $firstElement = $row['id'];
}
After that you can iterate over the list like this:
$curId = $firstElement;
while($curId != null) {
do_something($newArray[ $curId ])
$curId = $newArray[ $curId ]['next'];
}
For efficiency you might considering looking at your data hydrator (the function that get's the data from the database in the array) to see or it can add the id as array-key inmediately. Also you could with query sort order make sure that the first element is always the first element in the original array, making it easy to find that id.
Btw, don't call this a linkedlist implementation, as a linked list is characterized by an object reference to the next element (not an index).
Edit: One thing I didn't mention yet. If you want to have the sorted list in an array, then replace do_something($newArray[ $curId ]); with $array[] = $newArray[ $curId ];.
I believe this solution is much more transparent / quicker then most other solutions as this is costing two rounds over the whole array, or if you can integrate the first part in your hydration method without cost, only one iteration through the array.
I believe the built in usort will work nicely for the data structure you described.
edit: This doesn't work correctly. Please un-accept so I can delete it.
<?php
//$arr = your array as described
usort($arr, 'subkey_compare_next');
function subkey_compare_next($a, $b) {
$a = $a['next'];
$b = $b['next'];
if($a === null) {
return -1;
}
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
?>
This code will work
$a[0] = array('0', '00', '000');
$a[1] = array('1', '11', '111');
$a[2] = array('2', '22', '222');
$a[3] = array('3', '33', '333');
$a[4] = array('4', '44', '444');
$result = count($a);
echo $result; // print count
list ($result1, $result2, $result3) = $a[4]; // array to list
echo $result3; // print data in list