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 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. But the problem is once 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
CodePudding user response:
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 witharray_column
. This way, we can access any index withid
of a particular value in O(1) time. This also gives you an advantage for the value of theid
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'] = getLeafNodeValue($matches[1], $data);
}
return $data[$id]['data'];
}
print_r($data);
CodePudding user response:
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.
CodePudding user response:
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>",]
];
#step 1 make keys easy to access
$prepared = [];
foreach ($data as $set) {
$prepared[$set['id']] = $set;
}
#setp 2 replace values
$final = [];
do {
foreach ($data as $k => &$set) {
$set['data'] = preg_replace_callback('#(.*<(\d )>)#', function ($m) use ($prepared) {
return $prepared[$m[2]]['data'];
}, $set['data']);
}
if (strpos($set['data'], '>') === false) {
$final[$k] = $set;
unset($data[$k]);
}
} while (count($data));
ksort($final);
print_r($final);
But thats only save to run, if each entry has an valid <x>
setup, else the do/while will loop infinite.