Run throw array of associative array and find keys - php

I have two arrays that look like this:
(this one is ordered by value_max)
$max_values = [
["name" => "john", "id" => 5, "value_max" => 500],
["name" => "john", "id" => 3, "value_max" => 200],
...
];
$min_values = [
["name" => "john", "id" => 5, "value_min" => 100],
["name" => "john", "id" => 3, "value_min" => 150],
...
];
And I need to have a final array like this:
(This one stills need to be ordered by value_max, so I assume I could just overwrite the first array with the calculations done with the second)
$max_and_difference_values = [
["name" => "john", "id" => 5, "value_max" => 500, "difference_value" => 400],
["name" => "john", "id" => 3, "value_max" => 200, "difference_value" => 50 ],
...
];
My question is pretty straight: What is the best/effective way to run through both first arrays and build the last one. Assuming that the size of the arrays can be of around 150 elements.

To avoid looping through all arrays repeatedly, index one array by the field you want to merge on, i.e. the 'id' key:
$second = array_combine(array_map(function ($i) { return $i['id']; }, $second_array), $second_array);
Then looping through the other and comparing the values is pretty easy:
$third = array();
foreach ($first_array as $i) {
$third[] = $i + array('difference_value' => $i['value_max'] - $second[$i['id']]['value_min']);
}
If it's guaranteed that both arrays will have exactly matching keys, you don't even need the first step and just go by already existing keys.

This will sort your array. So you can sort the second array.
<?php
$min_values = array(
array("name" => "john", "id" => 5, "value_min" => 100),
array("name" => "john", "id" => 3, "value_min" => 150),
);
function aasort (&$array, $key) {
$sorter=array();
$ret=array();
reset($array);
foreach ($array as $ii => $va) {
$sorter[$ii]=$va[$key];
}
asort($sorter);
foreach ($sorter as $ii => $va) {
$ret[$ii]=$array[$ii];
}
$array=$ret;
}
aasort($min_values,"id");
echo "<pre>";
print_r($min_values);
echo "</pre>";
?>
And then you can use the logic what Alessandro Minoccheri mentioned.
$arr = array();
for ($i=0; $i<count($first_array);$i++){
$arr['name'] = $first_array[$i]['name'];
$arr['id'] = $first_array[$i]['id'];
$arr['value_max'] = $first_array[$i]['value_max'];
$arr['difference_value'] = $first_array[$i]['value_max']-$second[$i['id']]['value_max'] ;
}

As you have not shared how the second array with the minimum values is ordered (in itself and relative to the maximum values array), I'd say, index the minimum values by the id entry and then do the calculation in a second iteration. It should be fast enough, 150 elements is just "nothing":
$max_values = [
["name" => "john", "id" => 5, "value_max" => 500],
["name" => "john", "id" => 3, "value_max" => 200],
];
$min_values = [
["name" => "john", "id" => 5, "value_min" => 100],
["name" => "john", "id" => 3, "value_min" => 150],
];
$min_values_by_id = [];
foreach($min_values as $min) {
$min_values_by_id[$min['id']] = $min['value_min'];
}
$max_and_difference_values = $max_values;
foreach($max_and_difference_values as &$entry)
{
$entry['difference_value'] = $entry['value_max'] - $min_values_by_id[$entry['id']];
}
unset($entry);
print_r($max_and_difference_values);
This is just a straight forward example, nothing fancy. Demo

the following works for me. I'm not a PHP expert though, i.e. I'm not so familiar with cloning (note the empty clone array). Note that it only copies existing properties (if one array does contain a min_value and another does only contain a some other key)
In essence
function convertArr( $arr ) {
// Easy referencing without having to search through the arrays more than once for a matching id
$new_arr = array();
foreach( $arr as $array ) {
$new_arr[ $array['id'] ] = cloneArr( $array );
}
return $new_arr;
}
function merge( $arr1, $arr2 ) {
$convertedArr1 = convertArr( $arr1 );
$convertedArr2 = convertArr( $arr2 );
$arr = array();
// Based on the ordered array
foreach( $convertedArr1 as $array ) {
$id = $array['id'];
$tempArr = array();
foreach( $convertedArr1[ $id ] as $k => $v ) {
$tempArr[ $k ] = $v;
}
foreach( $convertedArr2[ $id ] as $k => $v ) {
$tempArr[ $k ] = $v;
}
array_push( $arr, $tempArr );
}
return $arr;
}
Full example:
<?php
//$arr1 = [ ["name" => "john", "id" => 5, "value_max" => 500], ["name" => "john", "id" => 3, "value_max" => 200] ];
//$arr2 = [ ["name" => "john", "id" => 5, "value_min" => 100], ["name" => "john", "id" => 3, "value_min" => 150] ];
$arr1 = array(
array( "name" => "john", "id" => 5, "value_max" => 500 ),
array( "name" => "john", "id" => 3, "value_max" => 200 )
);
$arr2 = array(
array( "name" => "john", "id" => 5, "value_min" => 100 ),
array( "name" => "john", "id" => 3, "value_min" => 150 )
);
function neatPrint( $arr ) {
echo "<pre>";
print_r( $arr );
echo "</pre>";
}
echo "<h2>Before</h2>";
neatPrint( $arr1 );
neatPrint( $arr2 );
echo "<hr/>";
function cloneArr( $old ) {
// I dunno how to properly clone
return $old;
}
function convertArr( $arr ) {
// Easy referencing without having to search through the arrays more than once for a matching id
$new_arr = array();
foreach( $arr as $array ) {
$new_arr[ $array['id'] ] = cloneArr( $array );
}
return $new_arr;
}
function merge( $arr1, $arr2 ) {
$convertedArr1 = convertArr( $arr1 );
$convertedArr2 = convertArr( $arr2 );
$arr = array();
// Based on the ordered array
foreach( $convertedArr1 as $array ) {
$id = $array['id'];
$tempArr = array();
neatPrint( $convertedArr1[ $id ] );
neatPrint( $convertedArr2[ $id ] );
foreach( $convertedArr1[ $id ] as $k => $v ) {
$tempArr[ $k ] = $v;
}
foreach( $convertedArr2[ $id ] as $k => $v ) {
$tempArr[ $k ] = $v;
}
array_push( $arr, $tempArr );
}
echo "<h2>Result</h2>";
return $arr;
}
echo "<h2>Loopthrough</h2>";
neatPrint( merge( $arr1, $arr2 ) );
?>
Let me know what you think :-)

if the element are in order try this code:
$arr = array();
for ($i=0; $i<count($first_array);$i++){
$arr['name'] = $first_array[$i]['name'];
$arr['id'] = $first_array[$i]['id'];
$arr['value_max'] = $first_array[$i]['value_max'];
$arr['difference_value'] = $first_array[$i]['value_max']-$second[$i['id']]['value_max'] ;
}

Related

Recursively replace placeholder substrings based on related row data

I have this array:
$data = [
["id" => 1, "data" => "data 1"],
["id" => 2, "data" => "data <4>"],
["id" => 3, "data" => "data 3"],
["id" => 4, "data" => "<3>"]
];
I want to produce a new array. The resulting array should be:
[
["id" => 1, "data" => "data 1"],
["id" => 2, "data" => "data data 3"],
["id" => 3, "data" => "data 3"],
["id" => 4, "data" => "data 3"]
]
The idea is that each time there is <number> in the data attribute then this value should be replaced by the data attribute in the element which has the same id. In the example above, the last element is: ["id" => 4, "data" => "<3>"]
So we replace <3> with data 3 since it is what is stored in the data attribute of element with id => 3.
I have already created a function that works with the above array:
public function refProcess($data, &$newData, $i, &$tmpData){
$dataLength = count($data);
if($i>=$dataLength){
return;
}
for(;$i<$dataLength;$i++){
if(is_null($tmpData)){
$tmpData = ['id'=> $data[$i]['id'], 'data'=>null];
}
if(strpos($data[$i]['data'],"[")!==false){
$parsed = $this->getInbetweenStrings("<", ">", $data[$i]['data']);
if(count($parsed)){
foreach($parsed as $occurance){
foreach($data as $key => $dataValue){
if($dataValue['id']==$occurance){
if(strpos($dataValue['data'], "<")!==false){
$this->refProcess($data, $newData, $key, $tmpData);
$tmpData=null;
}
else{
$tmpDataAtt = str_replace("<".$occurance.">", $dataValue['data'], $data[$i]['data']);
$tmpData['data'] = $tmpDataAtt;
$newData [] = $tmpData;
$tmpData = null;
break;
}
}
}
}
}
}
else{
$tmpData['data'] = $data[$i]['data'];
$newData [] = $tmpData;
$tmpData = null;
}
}//foreach
}
//returns an array contains strings existing between $start and $end. Multiple occurance
public function getInbetweenStrings($start, $end, $str){
$matches = array();
$regex = "/$start([a-zA-Z0-9_]*)$end/";
preg_match_all($regex, $str, $matches);
return $matches[1];
}
It works fine until I add another element to the array:
$data = [
["id" => 1, "data" => "data 1"],
["id" => 2, "data" => "data <4>"],
["id" => 3, "data" => "data 3"],
["id" => 4, "data" => "<3>"]
["id" => 5, "data" => "<2>"]
];
Element with id:5 the function goes into an endless loop. What am I missing?
The code can be tested at https://onlinephp.io/c/0da5d
Your code is too clumsy to begin with. I would rather suggest a much simpler approach.
Index your array with id value as it's key with array_column. This way, we can access any index with id of a particular value in O(1) time. This also gives you an advantage for the value of the id to be anything and not necessarily being symmetric with your subarray index key.
Capture the ID using regex. If there is a match, recurse again, else, our search ends here. Return it's data value to the parent call and you are done.
Snippet:
<?php
$data = array_column($data, null, 'id');
foreach($data as $id => $d){
getLeafNodeValue($id, $data);
}
function getLeafNodeValue($id, &$data){
$matches = [];
if(preg_match('/<(\d+)>/', $data[$id]['data'], $matches) === 1){
$data[$id]['data'] = preg_replace('/<(\d+)>/', getLeafNodeValue($matches[1], $data), $data[$id]['data']);
}
return $data[$id]['data'];
}
print_r($data);
Online Demo
You dont really do any recursive stuff, is a quit flat array structure you have.
UPDATE: This was wrong, keep it just for the related comments:
<?php
$data = [
["id" => 1, "data" => "data 1",],
["id" => 2, "data" => "data <4>",],
["id" => 3, "data" => "data 3",],
["id" => 4, "data" => "<3>",],
["id" => 5, "data" => "<2>",]
];
foreach ($data as &$set) {
$set['data'] = preg_replace_callback('#.*(\d+).*#', function ($m) {
return 'data ' . $m[1];
},$set['data']);
}
print_r($data);
UPDATE:
Here is now a working solution.
<?php
$data = [
["id" => 1, "data" => "data 1",],
["id" => 2, "data" => "data <4>",],
["id" => 3, "data" => "data 3",],
["id" => 4, "data" => "<3>",],
["id" => 5, "data" => "<2>",],
["id" => 6, "data" => "data <7>",],#loop to 7
["id" => 7, "data" => "<6>",],#loop to 6
["id" => 8, "data" => "<0>",],#dead link
["id" => 9, "data" => "<10>",],#multi level loop
["id" => 10, "data" => "data <11>",],#multi level loop
["id" => 11, "data" => "<9>",],#multi level loop
];
#just for testing: order can be ignored
shuffle($data);
#step 1 make keys easy to access
$prepared = [];
$tmp=[];
foreach ($data as $set) {
$prepared[$set['id']] = $set['data'];
$tmp[$set['id']] = $set;
}
$data=$tmp;
#setp 2 replace values
$final = [];
do {
foreach ($data as $k => &$set) {
$set['data'] = preg_replace_callback('#(.*)<(\d+)>#', function ($m) use ($prepared,$k) {
#check for dead links
if(!isset($prepared[$m[2]])){
return $m[1]." ?{$m[2]}?";
}
#check for loop refer
if(strpos($prepared[$m[2]],"<".$k.">")!==false){
return $m[1]." §{$m[2]}§";
}
return $m[1].$prepared[$m[2]];
}, $set['data']);
if (strpos($set['data'], '>') === false) {
$final[$k] = $set;
unset($data[$k]);
}
}
} while (count($data));
ksort($final);
print_r($final);
UPDATED:
Now checks for dead links or loops and marks them.
Added more prepare code, so order is now ignorable
Your code is way too complicated, there's an easier approach. Note, I reindex the initial data to the key:id -> value:array, to avoid all those loops for finding the entry for required id.
<?php
$data = [
[
"id"=>1,
"data"=>"data 1",
],
[
"id"=>2,
"data"=>"data <4>",
],
[
"id"=>3,
"data"=>"data 3",
],
[
"id"=>4,
"data"=>"<3>",
],
[
"id"=>5,
"data"=>"<2>",
]
];
function getValue( $arr, $id ){
// Required data contains placeholder, going deeper
if( preg_match("/<\d+>/", $arr[$id]['data']) ){
return preg_replace_callback("/<(?<source_id>\d+)>/", function( $matches ) use ($arr) {
return getValue( $arr, $matches['source_id'] );
}, $arr[$id]['data'] );
}
// Return raw value as-is
return $arr[$id]['data'];
}
// Reindexing to get convinient access
$indexed_data = [];
foreach( $data as $entry )
$indexed_data[ $entry['id'] ] = $entry;
// Resolving through recursive getValue function
foreach( $indexed_data as $id => &$entry )
$entry['data'] = getValue( $indexed_data, $id );
// Output in desired format
print_r( array_values( $indexed_data ) );
If there may be two entries with the same id, this approach not gonna work.
Here is a tidy recursive snippet that will modify your array by reference.
Loop over the array (I am choosing to use array de-structuring syntax to declare individual row variables.
If the needle is null or matches the row id, attempt the recursive replacement.
If the needle matches a row's id, return the value to the parent level, so that eventually the top level array is affected.
Code: (Demo)
function recurse(&$array, $needle = null) {
foreach ($array as ['id' => $id, 'data' => &$data]) {
if (($needle ?? $id) === $id) {
$data = preg_replace_callback(
'/<(\d+)>/',
fn($m) => recurse($array, (int) $m[1]),
$data
);
}
if ($needle === $id) {
return $data;
}
}
}
recurse($array);
var_export($array);

How to create dynamic combination with php?

I have 3 array like
$arr = [
"color" => [["name"=>"red"]],
"size" => [["name"=>"18 inch"], ["name"=>"15 inch"]],
"type" => [["name"=>"plastic"]]
]
$combo = array();
foreach ($arr['size'] as $size) {
foreach($arr['color'] as $color){
foreach ($arr['type'] as $type) {
$variant = json_encode(['size' => $size->name, 'color' =>
$color->name, 'type' => $type->name]);
array_push($combo,$variant);
}
}
}
echo $combo;
// result
0 => "{"size":"15 inch","color":"yellow","type":"metal"}"
1 => "{"size":"18 inch","color":"yellow","type":"plastic"}"
It works properly but but there is can be less or more variants. How can I handle this.
For example
$arr = [
"size" => [["name"=>"18 inch"], ["name"=>"15 inch"]],
"type" => [["name"=>"plastic"]]
]
Or
$arr = [
"color" => [["name"=>"red"]],
"size" => [["name"=>"18 inch"], ["name"=>"15 inch"]],
"type" => [["name"=>"plastic"]],
"brand" => [['name' => 'something']],
]
For what i understand, you have to combine the arrays of properties into one array of
object.
I have to leave now, but if you need a explanation leave a comment and i updated the answers
$arr = [
"color" => [["name"=>"red"],['name'=>'yellow']],
"size" => [["name"=>"18 inch"], ["name"=>"15 inch"]],
"type" => [["name"=>"plastic"]],
"brand" => [['name' => 'something']],
];
function runFor($arr ,&$array, $keys,$index,&$positions){
foreach ($arr[$keys[$index]] as $key => $espec){
$positions[$keys[$index]] = $key;
if($index + 1 < count($keys)){
runFor($arr,$array,$keys, $index+1,$positions);
}else{
$item = (object)[];
foreach ($keys as $key){
$item->$key = $arr[$key][$positions[$key]]['name'];
}
array_push($array,$item);
}
unset($positions[$keys[$index]]);
}
}
$array = array();
$keys = array_keys($arr);
$positions = [];
runFor($arr,$array,$keys,0,$positions);
$combo = array();
foreach ($array as $item){
array_push($combo,json_encode($item));
}
var_dump($combo);

How to expand an array based on quantity

I have an array which stores quantity. I want to convert this into individual array as described below
What i have tried
function expandArray($TICKETTYPE_TEMPARRAY, $readQuantity){
for($i=1; $i<=$readQuantity; $i++){
$TICKETTYPE[] = $TICKETTYPE_TEMPARRAY;
}
}
foreach($TICKETTYPE_TEMPARRAY as $key => $value){
$readQuantity = $value["QUANTITY"];
expandArray($TICKETTYPE_TEMPARRAY, $readQuantity);
}
My array
$myarray = array(
"TICKETPRICE" => "6.0000",
"QUANTITY" => "2",
"COUNTRYID" => "15"
)
Expected output:
[{TICKETPRICE:6.000, QUANTITY:2, COUNTRYID=15},
{TICKETPRICE:6.000, QUANTITY:2, COUNTRYID=15}]
I think you want something like this:
$myarray = array(array(
"TICKETPRICE" => "6.0000",
"QUANTITY" => "2",
"COUNTRYID" => "15"
),
array(
"TICKETPRICE" => "4.0000",
"QUANTITY" => "3",
"COUNTRYID" => "9"
));
$output = array();
foreach ($myarray as $array) {
$output[] = array_fill(0, $array['QUANTITY'], $array);
}
echo json_encode($output);
Output:
[[{"TICKETPRICE":"6.0000","QUANTITY":"2","COUNTRYID":"15"},
{"TICKETPRICE":"6.0000","QUANTITY":"2","COUNTRYID":"15"}
],
[{"TICKETPRICE":"4.0000","QUANTITY":"3","COUNTRYID":"9"},
{"TICKETPRICE":"4.0000","QUANTITY":"3","COUNTRYID":"9"},
{"TICKETPRICE":"4.0000","QUANTITY":"3","COUNTRYID":"9"}
]]
Or if you want a completely flat array:
$output = array();
foreach ($myarray as $array) {
$output = array_merge($output, array_fill(0, $array['QUANTITY'], $array));
}
echo json_encode($output);
Output:
[{"TICKETPRICE":"6.0000","QUANTITY":"2","COUNTRYID":"15"},
{"TICKETPRICE":"6.0000","QUANTITY":"2","COUNTRYID":"15"}
{"TICKETPRICE":"4.0000","QUANTITY":"3","COUNTRYID":"9"},
{"TICKETPRICE":"4.0000","QUANTITY":"3","COUNTRYID":"9"},
{"TICKETPRICE":"4.0000","QUANTITY":"3","COUNTRYID":"9"}
]
Demo on 3v4l.org
Simply array_fill and json_encode may help you.
<?php
$myarray = array(
"TICKETPRICE" => "6.0000",
"QUANTITY" => "2",
"COUNTRYID" => "15"
);
$myarray = json_encode(array_fill(0, $myarray['QUANTITY'], $myarray));
print '<pre>';
print_r($myarray);
?>

Multidimensional array to text using only keys

I have an array of this sort:
$array = [
"Darren" => [
"age" => "18",
"work" => [
"occupation" => "developer",
"company" => "ABC Ltd"
]
],
"John" => [
"age" => "24",
"work" => [
"occupation" => "developer",
"company" => "ABC Ltd",
"url" => "www.example.com"
],
]
]
And would like to merge the keys with a dot in between, depending on the array's hierachy:
"Darren.age"
"Darren.work.occupation"
"Darren.work.company"
...
The function that I made so far is
public function buildExpressionKey($array, $parentKey = null){
$expression = [];
foreach($array as $key=>$value){
if(is_array($value)){
array_push($expression, $parentKey. implode(".",
$this->buildExpressionKey($value, $key)));
}else{
array_push($expression, $key);
}
}
return $expression;
}
it is returning this value at the moment:
[
[0] => "age.Darrenoccupation.company"
[1] => "age.Johnoccupation.company.url"
]
Was wondering if it is possible to make a function which automatically does merges the keys like that, thanks in advance :)
What you are currently asking for:
<?php
$people =
[
'John' =>
[
'Occupation' => 'Developer',
'Age' => 18
],
'Darren' =>
[
'Occupation' => 'Manager',
'Age' => 40
]
];
foreach($people as $name => $value)
foreach($value as $k => $v)
$strings[] = $name . '.' . $k;
var_export($strings);
Output:
array (
0 => 'John.Occupation',
1 => 'John.Age',
2 => 'Darren.Occupation',
3 => 'Darren.Age',
)
Managed to resolve this issue :)
/**
* #param $array
* #return array
*/
public function buildExpressionKey($array){
$iterator = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($array));
$keys = array();
foreach ($iterator as $key => $value) {
// Build long key name based on parent keys
for ($i = $iterator->getDepth() - 1; $i >= 0; $i--) {
$key = $iterator->getSubIterator($i)->key() . '.' . $key;
}
$keys[] = $key;
}
return $keys;
}
Found something similar on here: Get array's key recursively and create underscore separated string

How to specifically choose a key and value to store in array PHP

I want to just store in result values from key 'code' instead of all including 'name' and 'id'
Here is the code:
<?php
$array = array(
array(
"name" => "a",
"code" => "416",
"id" => "a1"
),
array(
"name" => "a",
"code" => "522",
"id" => "a2"
),
array(
"name" => "b",
"code" => "580",
"id" => "b1"
)
);
$counts = array_count_values(
array_map(function (array $entry) { return $entry['name']; }, $array)
// or array_column($array, 'name') in PHP 5.5+
);
$uniqueNames = array_keys(
array_filter($counts, function ($count) { return $count == 1; })
);
$result = array_filter($array, function (array $entry) use ($uniqueNames) {
return in_array($entry['name'], $uniqueNames);
});
print_r($result);
Try this to see the result : http://3v4l.org/32JUL#v530
What I want is: store "code" : "580" only instead of all sets.
I improved this one, because there might be possibility to get more ['code'] values with unique ['name'] , this would not be in best fit here. I would apply a simple foreach to get desired result.
$array = array(
array(
"name" => "a",
"code" => "416",
"id" => "a1"
),
array(
"name" => "a",
"code" => "522",
"id" => "a2"
),
array(
"name" => "b",
"code" => "580",
"id" => "b1"
)
);
$counts = array_count_values(
array_map(function (array $entry) { return $entry['name']; }, $array)
// or array_column($array, 'name') in PHP 5.5+
);
$uniqueNames = array_keys(
array_filter($counts, function ($count) { return $count == 1; })
);
$result = array_filter($array, function (array $entry) use ($uniqueNames) {
return in_array($entry['name'], $uniqueNames);
});
foreach ($result as $res){
$uniqueCodes[] = $res['code'];
}
print_r($uniqueCodes);

Categories