Can I determine PHP array value deltas? - php

I have two multidimensional arrays that I need to determine the delta for each value. I know the array_diff function only returns the difference in keys. Is there a functon that will determine the delta for each set of values assuming the two arrays contain the same set of keys?
Example:
array_1(test1 => Array([key1] => 100, [key2] => 200 ) )
array_2(test1 => Array([key1] => 105, [key2] => 195 ) )
I would expect something like:
array_3(test1 => Array([key1] => 5, [key2] => -5 ) )
Are there any PHP methods to do this or am I on my own?

Answers here suggested using foreach loop but I think creating anonymous function will be easier:
<?php
$count_delta = create_function('$a,$b', 'return $a - $b;');
$arr1 = array(100, 200);
$arr2 = array(20, 180);
$delta = array_map($count_delta, $arr1, $arr2);
var_dump($delta);
Output will be:
array
0 => int 80
1 => int 20

$delta = array();
foreach( $array1 as $k=>$v )
{
if( array_key_exists( $k, $array2 )
{
// preserve the key
$delta[$k] = $array1[$k] - $array2[$k];
// or don't
$delta[] = $array1[$k] - $array2[$k];
}
}
print_r($delta);

There is no built-in function for that, but you can use this.
function delta_array($a, $b) {
if (sizeof($a) != sizeof($b))
return false;
$arr = array();
for ($i=0; $i < $c = sizeof($a); $i++)
$arr[] = $b[$i] - $a[$i];
return $arr;
}
$arr1 = array(100,200);
$arr2 = array(105,195);
$delta = delta_array($arr1, $arr2);
print_r($delta);
The above will return
Array
(
[0] => -5
[1] => 5
)

Related

PHP how to combine array and create new array

I have 3 arrays like this :
$first = array(
[0] => 13
[1] => 66
[2] => 15
)
$second = array
(
[0] => append
[1] => prepend
[2] => append
)
$third = array
(
[0] => 2
[1] => 4
[2] => 1
)
Now I want to combine these 3 array and create new SINGLE array like this :
I want to get value from each array and combine it into one.
$new_array = array(
[0] = array (
'page'=>13,
'position'=>'append'
'priority'=>'2'
)
[1] = array (
'page'=>66,
'position'=>'prepend'
'priority'=>'4'
)
[2] = array (
'page'=>15,
'position'=>'append'
'priority'=>'1'
)
)
How to do this ?
You can use array_map() to traverse multiple arrays(of the same length) and build a new array from them, item by item:
$first = array(13, 66, 15);
$second = array('append', 'prepend', 'append');
$third = array(2, 4, 1);
print_r(
array_map(
function($page, $position, $priority) {
return compact('page', 'position', 'priority');
},
$first,
$second,
$third
)
);
Though in this case with a trivial structure foreach() might be an alternative.
Edit: Version 2. Some might argue that this is worse because of readability and hence code maintainability. Thanks to AbraCadaver
$keys = ['page', 'position', 'priority'];
// Note: The order in $keys and array_map() arguments must be the same
// On the other hand you only need to type the keys once and it's easy
// to change the number of arguments :)
print_r(
array_map(
function(...$args) use ($keys) {
return array_combine($keys, $args);
},
$first,
$second,
$third
)
);
Edit: Version 3. Using slightly more modern PHP (7.4) with arrow functions:
print_r(
array_map(
fn(...$args) => array_combine($keys, $args),
$first,
$second,
$third
)
);
Just map each element of each array and return an array with the keys:
$result = array_map(function($f, $s, $t) {
return ['page'=>$f, 'position'=>$s, 'priority'=>$t];
}, $first, $second, $third);
Or define the keys outside of the function and combine with each element:
$keys = ['page', 'position', 'priority'];
$result = array_map(function($f, $s, $t) use($keys) {
return array_combine($keys, [$f, $s, $t]);
}, $first, $second, $third);
If you have same column, you can do it like this
$first = [13,66,15];
$second = ['append','prepend','append'];
$third = [2,4,1];
foreach($first as $v => $i){
$new_array[] = [
'page' => $i,
'position' => $second[$v],
'priority' => $third[$v],
];
}
print_r($new_array);
this is the result

PHP Left rotate array in PHP

I am trying to left rotate an array in PHP 2 times. It works correctly but there is a blank space in the array from some unknown reason. This is my code:
$a_temp = fgets($handle);
$a = explode(" ",$a_temp);
for($i = 0; $i < 2; $i++){
print_r($a);
array_unshift($a, array_pop($a));
print_r($a);
}
The file has something like this:
1 2 3
Now the output I get is:
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 3
[1] => 1
[2] => 2
)
Array
(
[0] => 3
[1] => 1
[2] => 2
)
Array
(
[0] => 2
[1] => 3
[2] => 1
)
As you can see, every time a rotate is performed, it introduces a blank space in it and during printing the array, it appears as a new line character. Any ideas what I am doing wrong?
As pointed out by #cHao ans #Kulvar, your "3" element is in fact "3\n" because your line returned by fgets ends with a \n which is normal.
Replace $a = explode(" ",$a_temp); with $a = explode(" ",trim($a_temp)); and you're fixed. Works with any string, numeric or not
It seems you have a non-integer value in there. Use array_map() to cast all values to integers.
$a_temp = fgets($handle);
$a = explode(" ",$a_temp);
$a = array_map("intval", $a); //Cast all values to integers
for($i = 0; $i < 2; $i++){
print_r($a);
array_unshift($a, array_pop($a));
print_r($a);
}
https://eval.in/724912
Combine the array_push() and array_shift() function. In this solution you would have to input a number of rotations, or you change the $k variable in the for-loop to your desired amount of rotations.
for($i=0; $i<$k; $i++){
//remove first element
$firstElement = array_shift($a);
//push first element to the end
array_push($a, $firstElement);
}
echo implode(" ", $a);
The first method is the optimised code. But other two are solution but not optimise as the first one.
function rotate_left_method_1( $a, $d ) {
$first = array_slice( $a, 0, $d );
$second = array_slice( $a, $d, null );
return array_merge( $second, $first );
}
function rotate_left_method_2( $a, $d ) {
$swap = 1;
foreach ( $a as $v ) {
if ( $d >= $swap ) {
array_shift( $a );
array_push( $a, $v );
$swap ++;
}
}
return $a;
}
function rotate_left_method_3( $a, $d ) {
if ( is_array( $a ) ) {
for ( $swap = 0; $swap < $d; $swap ++ ) {
$temp = $a[0];
array_shift( $a );
array_push( $a, $temp );
}
}
return $a;
}
var_dump( rotate_left_method_1( array( 1, 2, 3, 4, 5 ), 4 ) );
var_dump( rotate_left_method_2( array( 1, 2, 3, 4, 5 ), 4 ) );
var_dump( rotate_left_method_3( array( 1, 2, 3, 4, 5 ), 4 ) );
function rotateLeft($d, $arr) {
$remaining = array_slice($arr, $d);
array_splice($arr, $d);
return array_merge($remaining,$arr);
}
$d =4; $arr = [1,2,3,4,5];
$result = rotateLeft($d, $arr);
var_dump($result);
output [5, 1, 2, 3, 4]

Retrieve and remove duplicate values from an associative array

I have associative array like below
$arr = [1=>0, 2=>1, 3=>1, 4=>2, 5=>2, 6=>3]
I would like to remove the duplicate values from the initial array and return those duplicates as as a new array of duplicate arrays. So I would end up with something like;
$arr = [1=>0, 6=>3]
$new_arr = [[2=>1, 3=>1],[4=>2, 5=>2]]
Does PHP provide such a function or if not how would I achieve this?
I've tried;
$array = [];
$array[1] = 5;
$array[2] = 5;
$array[3] = 4;
$array[5] = 6;
$array[7] = 7;
$array[8] = 7;
$counts = array_count_values($array);
print_r($counts);
$duplicates = array_filter($array, function ($value) use ($counts) {
return $counts[$value] > 1;
});
print_r($duplicates);
$result = array_diff($array, $duplicates);
print_r($result);
This outputs;
[1] => 5
[2] => 5
[7] => 7
[8] => 7
&
[3] => 4
[5] => 6
which is almost what I want.
Code
The following works for me... Tho I make no promises in regards to complexity and performance, but there's the general idea... Also, I haven't written PHP for many years now, so bear that in mind.
<?php
function nubDups( $arr ) {
$seen = [];
$dups = [];
foreach ( $arr as $k => $v) {
if ( array_key_exists( $v, $seen ) ) {
// duplicate found!
if ( !array_key_exists( $v, $dups ) )
$dups[$v] = [$seen[$v]];
$dups[$v][] = $k;
} else
// First time seen, record!
$seen[$v] = $k;
}
$uniques = [];
foreach ( $seen as $v => $k ) {
if ( !array_key_exists( $v, $dups ) ) $uniques[$k] = $v;
}
return [$uniques, $dups];
}
function nubDups2( $arr ) {
for ( $seen = $dups = []; list( $k, $v ) = each( $arr ); )
if ( key_exists( $v, $dups ) ) $dups[$v][] = $k;
else if ( key_exists( $v, $seen ) ) $dups[$v] = [$seen[$v], $k];
else $seen[$v] = $k;
return [array_flip( array_diff_key( $seen, $dups ) ), $dups];
}
$arr = [0, 1, 4, 1, 2, 2, 3];
print_r( nubDups( $arr ) );
print_r( nubDups2( $arr ) );
Output (for both)
$ php Test.php
Array
(
[0] => 0
[2] => 4
[6] => 3
)
Array
(
[1] => Array
(
[0] => 1
[1] => 3
)
[2] => Array
(
[0] => 4
[1] => 5
)
)
Shortened
removed, specified as [(k, v)]: [(0, 0), (2, 4), (6, 3)]
duplicates, specified as [(v, [k])]: [(1, [1, 3]), (2, [4, 5])]
In Haskell
This version abuses hash tables for fast lookups.
A simpler version that almost does the same but ignores indexes, written in haskell:
-- | 'nubDupsBy': for a given list yields a pair where the fst contains the
-- the list without any duplicates, and snd contains the duplicate elements.
-- This is determined by a user specified binary predicate function.
nubDupsBy :: (a -> a -> Bool) -> [a] -> ([a], [a])
nubDupsBy p = foldl f ([], [])
where f (seen, dups) x | any (p x) seen = (seen, dups ++ [x])
| otherwise = (seen ++ [x], dups)

Extract negative and non-negative values from an array

I need to divide an array into two arrays.
One array will contain all positive values (and zeros), the other all negative values.
Example array:
$ts = [7,-10,13,8,0,4,-7.2,-12,-3.7,3.5,-9.6,6.5,-1.7,-6.2,7];
Negatives result array:
[-10,-7.2,-12,-3.7,-9.6,-1.7,-6.2];
Non-negatives result array:
[7,13,8,0,4,3.5,6.5,7];
Without using any array functions..
Pretty straightforward. Just loop through the array and check if the number is less than 0, if so , push it in the negative array else push it in the positive array.
<?php
$ts=array(7,-10,13,8,4,-7.2,-12,-3.7,3.5,-9.6,6.5,-1.7,-6.2,7);
$pos_arr=array(); $neg_arr=array();
foreach($ts as $val)
{
($val<0) ? $neg_arr[]=$val : $pos_arr[]=$val;
}
print_r($pos_arr);
print_r($neg_arr);
OUTPUT :
Array
(
[0] => 7
[1] => 13
[2] => 8
[3] => 4
[4] => 3.5
[5] => 6.5
[6] => 7
)
Array
(
[0] => -10
[1] => -7.2
[2] => -12
[3] => -3.7
[4] => -9.6
[5] => -1.7
[6] => -6.2
)
You can use array_filter function,
$positive = array_filter($ts, function ($v) {
return $v > 0;
});
$negative = array_filter($ts, function ($v) {
return $v < 0;
});
Note: This will skip values with 0, or you can just change condition to >=0 in positive numbers filter to considered in positive group.
DEMO.
The most elegant is to use phps array_filter() function:
<?php
$ts = [ 7,-10,13,8,4,-7.2,-12,-3.7,3.5,-9.6,6.5,-1.7,-6.2,7 ];
print_r( array_filter( $ts, function( $val ) { return (0>$val); } ) );
print_r( array_filter( $ts, function( $val ) { return ! (0>$val); } ) );
?>
If you are still using an older php version you need some longer implementation:
<?php
$ts = array( 7,-10,13,8,4,-7.2,-12,-3.7,3.5,-9.6,6.5,-1.7,-6.2,7 );
print_r( array_filter( $ts, create_function( '$val', 'return (0>$val);' ) ) );
print_r( array_filter( $ts, create_function( '$val', 'return ! (0>$val);' ) ) );
?>
Food for thought, you could write a generic function that splits an array based on a boolean result:
// splits an array based on the return value of the given function
// - add to the first array if the result was 'true'
// - add to the second array if the result was 'false'
function array_split(array $arr, callable $fn)
{
$a = $b = [];
foreach ($arr as $key => $value) {
if ($fn($value, $key)) {
$a[$key] = $value;
} else {
$b[$key] = $value;
}
}
return [$a, $b];
}
list($positive, $negative) = array_split($ts, function($item) {
return $item >= 0;
});
print_r($positive);
print_r($negative);
Demo
Rather than declaring two arrays, I recommend declaring one array with two subarrays. You can either give the subarrays an index of 0 or 1 depending on the conditional evaluation with zero, or you can go a little farther by declaring a lookup array to replace the integer key with an expressive word.
Regardless of if you choose to create one array or two arrays, you should only make one loop over the input array. Making two loops or by calling array_filter() twice is needlessly inefficient.
Code: (Demo)
$ts = [7,-10,13,8,4,-7.2,-12,-3.7,3.5,-9.6,6.5,-1.7,-6.2,7];
const KEY_NAMES = [0 => 'negative', 1 => 'non-negatuve'];
$result = [];
foreach ($ts as $v) {
$result[KEY_NAMES[$v >= 0]][] = $v;
}
var_export($result);
Output:
array (
'non-negatuve' =>
array (
0 => 7,
1 => 13,
2 => 8,
3 => 4,
4 => 3.5,
5 => 6.5,
6 => 7,
),
'negative' =>
array (
0 => -10,
1 => -7.2,
2 => -12,
3 => -3.7,
4 => -9.6,
5 => -1.7,
6 => -6.2,
),
)

Combine arrays to form multidimensional array in php

I know there's a ton of answers but I can't seem to get it right. I have the following arrays and what I've tried:
$a = array ( 0 => '1421' , 1 => '2241' );
$b = array ( 0 => 'teststring1' , 1 => 'teststring2' );
$c = array ( 0 => 'teststring3' , 1 => 'teststring4' );
$d = array ( 0 => 'teststring5' , 1 => 'teststring6' );
$e = array_combine($a, array($b,$c,$d) );
But with this I get the error array_combine() [function.array-combine]: Both parameters should have an equal number of elements.
I know it's because the $a's array values aren't keys. That's why I'm coming here to see if I could get some help with an answer that can help me make it look something like this:
array(2) {
[1421]=>array( [0] => teststring1
[1] => teststring3
[2] => teststring5
)
[2241]=>array( [0] => teststring2
[1] => teststring4
[2] => teststring6
)
}
If you have control over creating the arrays, you should create them like:
$a = array ('1421' ,'2241');
$b = array ('teststring1', 'teststring3', 'teststring5');
$c = array ('teststring2', 'teststring4', 'teststring6');
$e = array_combine($a, array($b,$c) );
If not, you have to loop over them:
$result = array();
$values = array($b, $c, $d);
foreach($a as $index => $key) {
$t = array();
foreach($values as $value) {
$t[] = $value[$index];
}
$result[$key] = $t;
}
DEMO
Here is a one-liner in a functional coding style. Calling array_map() with a null function parameter followed by the "values" arrays will generate the desired subarray structures. array_combine() does the key=>value associations.
Code (Demo)
var_export(array_combine($a, array_map(null, $b, $c, $d)));
Output:
array (
1421 =>
array (
0 => 'teststring1',
1 => 'teststring3',
2 => 'teststring5',
),
2241 =>
array (
0 => 'teststring2',
1 => 'teststring4',
2 => 'teststring6',
),
)
Super clean, right? I know. It's a useful little trick when you don't have control of the initial array generation step.
Here's a new version of array_merge_recursive which will handle integer keys. Let know how it goes.
$a = array ( 0 => '1421' , 1 => '2241' );
$b = array ( 0 => 'teststring1' , 1 => 'teststring2' );
$c = array ( 0 => 'teststring3' , 1 => 'teststring4' );
$d = array ( 0 => 'teststring5' , 1 => 'teststring6' );
$e = array_combine($a, array_merge_recursive2($b, $c, $d));
echo "<pre>";
print_r($e);
function array_merge_recursive2() {
$args = func_get_args();
$ret = array();
foreach ($args as $arr) {
if(is_array($arr)) {
foreach ($arr as $key => $val) {
$ret[$key][] = $val;
}
}
}
return $ret;
}

Categories