PHP array_udiff weird behavior - php

I have 2 arrays, $array0 and $array1. Let's pre-fill them:
$user01 = array("no" => 1, "name" => "john");
$user02 = array("no" => 2, "name" => "lewis");
$user03 = array("no" => 3, "name" => "dan");
$array0 = array($user01, $user02, $user03, $user04);
$user11 = array("id" => 1, "name" => "john", "attr" => "foo");
$user12 = array("id" => 7, "name" => "mark", "attr" => "bar");
$array1 = array($user11, $user12);
I want to get all users from $array0 who are not in $array1, so I use array_udiff:
$diff = array_udiff($array0, $array1, function ($userA, $userB) {
return $userA['no'] == $userB['id'];
});
However, inside the anonymous compare function, if I do a var_dump of $userA and $userB, they both seem to belong to $array0, while the behavior I was expecting is for $userA to belong to $array0 and $userB to $array1.
I.e., the expected answer is [[2, "lewis"], [3, "dan"]], but I get a 'not found index': "Undefined index id" in the line of the comparison function.
Am I missing something on array_udiff behavior?

I'm pretty sure PHP expects the arguments to the comparison function to be interchangeable, you either need to change the array indexes to be common, or implement logic in the comparison function to deal with this.
That's not how a comparison function works.
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.
So:
$user01 = array("id" => 1, "name" => "john"); // "no" changed to "id"
$user02 = array("id" => 2, "name" => "lewis");
$user03 = array("id" => 3, "name" => "dan");
$array0 = array($user01, $user02, $user03); // non-existant $user04 removed
$user11 = array("id" => 1, "name" => "john", "attr" => "foo");
$user12 = array("id" => 7, "name" => "mark", "attr" => "bar");
$array1 = array($user11, $user12);
$diff = array_udiff($array0, $array1, function ($userA, $userB) {
if( $userA['id'] == $userB['id'] ) { return 0; } // compare function re-jiggered
else { return $userA['id'] - $userB['id']; }
});
print_r($diff);
Yields:
Array
(
[1] => Array
(
[id] => 2
[name] => lewis
)
[2] => Array
(
[id] => 3
[name] => dan
)
)

Related

How to change the way php returns the results of a sum with very small values

you will see maybe this problem sounds silly but it has tended me a bit stuck.
You see, I have a function which receives an array, with this array a sum must be made, specifically sum one of its columns (I enter as the third parameter of the function the column I want to sum).This work good. The problem is the way it generates the result. I show you my code:
function sumArray($array, $index, $col) {
$returnArray = []; // temporary container
// sanity checks
if (!is_array($array)) {
return 'error not an array';
}
$firstRow = reset($array);
if (!array_key_exists($index, $firstRow) || !array_key_exists($col, $firstRow)) {
return 'error keys provided not found';
}
foreach ($array as $value) {
if (!isset($returnArray[$value[$index]])) { // initialize
$returnArray[$value[$index]] = [$index => $value[$index], $col => 0];
}
// add value
$returnArray[$value[$index]][$col] += $value[$col]; //here is the sum
}
return $returnArray;
}
$products = array ( //this is the array
array("Id" => "000001",
"Name" => "Cheese",
"Quantity" => "0.00000012",
"Price" => "10"),
array("Id" => "000001",
"Name" => "Cheese",
"Quantity" => "0.00000123",
"Price" => "20"),
array("Id" => "000001",
"Name" => "Cheese",
"Quantity" => "0.00000020",
"Price" => "30"),
array("Id" => "000002",
"Name" => "Ham",
"Quantity" => "0.00000346",
"Price" => "200"),
array("Id" => "000002",
"Name" => "Ham",
"Quantity" => "0.000000998",
"Price" => "100"),
array("Id" => "000003",
"Name" => "Baicon",
"Quantity" => "0.000000492",
"Price" => "900")
);
$summedArray = sumArray($products, 'Name', 'Quantity');
print_r($summedArray);
the result of my sum is a new array. But look at the Quantity column:
Array (
[Cheese] => Array ( [Name] => Cheese [Quantity] => 1.55E-6 )
[Ham] => Array ( [Name] => Ham [Quantity] => 4.458E-6 )
[Baicon] => Array ( [Name] => Baicon [Quantity] => 4.92E-7 )
)
This form: 4.458E-6 I don't like. I would like something like this: 0.000004458
Do you know what I could do to get something like that? A suggestion would be of great help.
It isn't really related to the fact that it's a sum. That's just the default string representation of a small float in PHP. Try this, for example:
$number = 0.000004458;
echo $number; // displays 4.458E-6
The only reason the appearance changes in your output is that those values are strings in your input array, but when you add them together they are converted to floats.
If you want those values to display differently, you'll need to use some kind of formatting function that returns a string.
Assuming you aren't going to be using print_r to display the value in your final product, you can use number_format or printf to display it with however many decimal points you'd like.
foreach ($summedArray as $product => $info) {
echo number_format($info['Quantity'], 9) . PHP_EOL;
// or printf('%.9f', $info['Quantity']) . PHP_EOL;
}

What is a better way to replace IDs in an array with their value counterpart?

I have the following array that includes id:
[Key1] => 1
[Key2] => 2, 3
I would like to replace these ids by their respective name from this second array:
[0] => Array
(
[ID] => 1
[Name] => Name1
)
[1] => Array
(
[ID] => 2
[Name] => Name2
)
[2] => Array
(
[ID] => 3
[Name] => Name3
The desired output:
[Key1] => Name1
[Key2] => Name2, Name3
I have the following code which works but I know this is not the right way. If anybody could let me know what would be a better way to achieve this, it would be greatly appreciated.
What my code looks like:
$var1 = explode(", ", $array1["Key1"]); // No need to explode in this example but "Key1" sometimes includes more than 1 ID
$var2 = explode(", ", $array1["Key2"]);
$array1["Key1"] = $var1 ; // This row is for the new array generated from "explode" to be a sub-array
$array1["Key2"] = $var2 ; // Same
for ($i = 0; $i < 83; $i++){
if($array1["Key1"][0] == $array2[$i]["ID"]){
$array1["Key1"][0] = $array2[$i]["Name"];
}
if($array1["Key1"][1] == $array2[$i]["ID"]){
$array1["Key1"][1] = $array2[$i]["Name"];
}
// (etc)
if($array1["Key2"][0] == $array2[$i]["ID"]){
$array1["Key2"][0] = $array2[$i]["Name"];
}
if($array1["Key2"][1] == $array2[$i]["ID"]){
$array1["Key2"][1] = $array2[$i]["Name"];
}
// (etc)
}
$var1 = implode(", ", $array1["Key1"]);
$var2 = implode(", ", $array1["Key2"]);
$array1["Key1"] = $var1 ;
$array1["Key2"] = $var2 ;
Just extract the ID and Name into a single-dimension and use it as search and replace parameters. We need to modify the IDs to search for and turn them into a pattern /\b$v\b/ where \b is a word boundary, so that 1 won't replace the 1 in 164 for example:
$replace = array_column($array2, 'Name', 'ID');
$search = array_map(function($v) { return "/\b$v\b/"; }, array_keys($replace));
$array1 = preg_replace($search, $replace, $array1);
You need to nest some loops. Here is a sample that should work:
//Processing Array
$arrayOne = array(
"Key1" => "1",
"Key2" => "2, 3");
//Lookup Array
$arrayTwo = array(
array(
"ID" => "1",
"Name" => "Name1"),
array(
"ID" => "2",
"Name" => "Name2"),
array(
"ID" => "3",
"Name" => "Name3"));
var_dump($arrayOne);
//Loop through all values in our original array
foreach($arrayOne as &$arrValue) {
//Split the value in the original array into another temporary array
//if there are multiple values.
$valueArray = explode(", ", $arrValue);
$outputArray = array();
foreach($valueArray as &$myValue) {
//Now do a lookup to replace each value
foreach($arrayTwo as &$lookupValue) {
//Find a match
if($myValue==$lookupValue["ID"]) {
$myValue = $lookupValue["Name"];
//We found the value we want, so let's break out of this loop
break;
}
}
//Append the value
array_push($outputArray, $myValue);
}
//Convert back to string
$arrValue= implode(", ", $outputArray);
}
var_dump($arrayOne);
There are improvements you could possibly make to this code if your incoming data was always sorted, but I imagine that is just the case for your sample above.
I have an approach to do this. You can make a try if you wish see here at:- https://eval.in/839823. I am using array_column to map the key=>value pair and then simple used foreach.
<?php
$main = ['Key1' => 1,'Key2' => '2, 3'];
$match = [
[
'ID' => 1,
'Name' => 'Name1'
],
[
'ID' => 2,
'Name' => 'Name2'
],
[
'ID' => 3,
'Name' => 'Name3'
]
];
$final_array=[];
$mapped = array_column($match, 'Name', 'ID');
foreach($main as $k=>$v){
$r = explode(',',$v);
if(count($r)>1){
$final_array[$k] = $mapped[$r[0]]. ", ".$mapped[intval($r[1])];
}else{
$final_array[$k] = $mapped[$r[0]];
}
}
print '<pre>';
//print_r($mapped);
print_r($final_array);
print '</pre>';
Output :
Array
(
[Key1] => Name1
[Key2] => Name2,Name3
)
Edit : As per comment of Josh Maag,
My code will only work if he only has a maximum of 2 values in Key2.
If Key3 contains "4,5,6" this code will leave the 6 untouched.
<?php
$main = ['Key1' => 1,'Key2' => '2,3','Key3' => '4,5,6'];
$match = [
[
'ID' => 1,
'Name' => 'Name1'
],
[
'ID' => 2,
'Name' => 'Name2'
],
[
'ID' => 3,
'Name' => 'Name3'
],
[
'ID' => 4,
'Name' => 'Name4'
],
[
'ID' => 5,
'Name' => 'Name5'
],
[
'ID' => 6,
'Name' => 'Name6'
]
];
$final_array=[];
$mapped = array_column($match, 'Name', 'ID');
foreach($main as $k=>$v){
$r = explode(',',$v);
if(count($r)>1){
$final_array[$k] = implode(',',array_map(function($key) use ($mapped){ return $mapped[$key]; }, array_values($r)));
}else{
$final_array[$k] = $mapped[$r[0]];
}
}
print '<pre>';
print_r($mapped);
print_r($final_array);
print '</pre>';
?>
See demo See here https://eval.in/839939
The core function that should be used for this task is preg_replace_callback(). Why? Because it is uniquely qualified to handle this operation in a single function call. It seems a tragedy to not use php functions for their designed purpose.
Beyond preg_replace_callback(), only array_column() is needed to prepare the $array2 data as a simple lookup array.
Code: (Demo)
$array1=["Key1"=>"1","Key2"=>"22, 4, 123"];
$array2=[["ID"=>"1","Name"=>"Name1"],["ID"=>"22","Name"=>"Name22"],["ID"=>"123","Name"=>"Name123"]];
$lookup=array_column($array2,'Name','ID'); // generate array: keys = IDs, vals = Names
$result=preg_replace_callback('/\d+/',function($m)use($lookup){return isset($lookup[$m[0]])?$lookup[$m[0]]:"*{$m[0]}*";},$array1);
var_export($result);
Output:
array (
'Key1' => 'Name1',
'Key2' => 'Name22, **4**, Name123',
)
There is no need to run any preparations (excluding $lookup) using additional loops or function calls.
This pattern will match all full ID numbers from each element in $array1 and process them individual. Each numeric match is sent to the anonymous callback function to receive its customized replacement string -- delivered by the $lookup data.
As an additional consideration, I have included an asterisk-wrapped replacement when an ID is not found in $lookup.

sum an especial index in array

How can I sum an especial index in array for example I wanna sum -age(it's an index) in this array:
$users = array(
"user1" => array("name" => "Loghman Avand", "age" => 26),
"user2" => array("name" => "Sara Alavi", "age" => 34),
"user3"=> array("name" => "Hossein Ahmadi", "age" => 3)
);
Use array_column() and array_sum():
$ages = array_column($users, 'age');
echo 'Sum is: ', array_sum($ages);
This is ignoring the fact your keys in the outer array are the same.
First thing is your above array is invalid. Because same array key ('users') is used mode than one time.
Check this below code
$users = array("user1" => array("name" => "Loghman Avand", "age" => 26), "user2" => array("name" => "Sara Alavi", "age" => 34), "user3" => array("name" => "Hossein Ahmadi", "age" => 3));
$age_sum = 0;
foreach($users as $user){
$age_sum += $user['age'];
}
echo $age_sum;

Sorting array by value

I have this array:
array(
"tour_0" => 1446,
"tour_1" => 1471,
"date-from-1471" => "2014-08-07",
"date-to-1471" => "2014-08-15",
"tour_2" => 30,
"date-from-30" => 2014-08-01,
"date-to-30" => 2014-08-05,
"tour_3" => 10
)
Now, i need it to be sorted to this:
array(
"0" => array("ID" => 1446),
"1" => array("ID" => 1471, "from" => "2014-08-07", "to" => "2014-08-15"),
"2" => array("ID" => 30, "from" => "2014-08-07", "to" => "2014-08-15"),
"3" => array("ID" => 10),
)
How can i accomplish this thing?
I've tried all sorts of things but i can't seem to figure this one out...
Thank's and sorry about the title but i just don't know how to describe it.
How about this?
$ret = [];
foreach($inputArray as $key => $value) {
if (preg_match('/^tour_([0-9]+)/', $key)) {
$ret[$value] = ["ID" => $value];
}
if (preg_match('/date-from-([0-9]+)/', $key, $matches)) {
$ret[$matches[1]]["from"] = $value;
}
if (preg_match('/date-to-([0-9]+)/', $key, $matches)) {
$ret[$matches[1]]["to"] = $value;
}
}
print_r($ret);
/*
Array
(
"1446" => Array ("ID" => 1446),
"1471" => Array ("ID" => 1471, "from" => "2014-08-07", "to" => "2014-08-15"),
"30" => Array ("ID" => 30, "from" => "2014-08-01", "to" => "2014-08-05"),
"10" => Array ("ID" => 10)
)*/
Close enough? (it is quite trival change the keys of the array, considering they are in order (0, 1, 2, 3, ...), if they are not, maybe you can save the order also (in another item of the subarray) and recompose again once this array is formed)

PHP: How to update/extend an multidimensional Array recursive and on-the-fly?

I've a multidimensional array and would like to update/add some values, recursive. I'd like to avoid building a new array, if possible. But I've two main problems:
How to update the values "on-the-fly"? It should be possible with the &-operator.
How to get values to extend the array if the function getDetails() is out of scope.
My alternative is to rebuild everything, but I think there should be a more clean possibility.
I've added some pseudocode and hope it's not too weird. I appreciate your help!
Here's the fiddle: http://phpfiddle.org/main/code/m2g-ign
PHP
// build the array
$myArray = array(
array(
"id" => 1,
"name" => "A (Level 1)",
"children" => array(
array(
"id" => 3,
"name" => "C (Level 2)",
"children" => array(
"id" => 4,
"name" => "D (Level 3)",
"children" => null
)
),
array(
"id" => 6,
"name" => "F (Level 2)",
"children" => array(
"id" => 7,
"name" => "G (Level 3)",
"children" => null
)
)
)
),
array(
"id" => 2,
"name" => "B (Level 1)",
"children" => array(
array(
"id" => 5,
"name" => "E (Level 2)",
"children" => null
)
)
)
);
// returns detailed data, important: it's out of scope
function getDetails($id) {
// select dataset from DB
// ...
return $details;
}
// loop the array
$RAI = new RecursiveArrayIterator($myArray);
function updateMyArray($iterator) {
while ($iterator->valid()) {
if ($iterator->hasChildren()) {
// recursive
updateMyArray($iterator->getChildren());
} else {
/*
// 1. set "name" to uppercase
// Pseudocode:
$iterator->activeChild()->change(function(&$value) {
$value = toUpperCase($value);
});
// 2. add Array
// Pseudocode:
$id = $iterator->activeChild()->getValue("id");
$iterator->activeChild()->add("details", getDetails($id)); // getDetails() is out of scope, I guess
*/
}
$iterator->next();
}
}
echo "<strong>Before:</strong><pre>";
print_r($myArray);
echo "</pre>";
iterator_apply($RAI, 'updateMyArray', array($RAI));
echo "<strong>After:</strong><pre>";
print_r($myArray);
echo "</pre>";

Categories