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
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>",],
["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
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:
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:
Here is a tidy recursive snippet that will modify ypur array by reference.
Loop over the array (I am choosing to use array destructuring 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);