Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 800
xoxaro Avatar asked Sep 17 '25 13:09

xoxaro


2 Answers

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

like image 96
nice_dev Avatar answered Sep 19 '25 04:09

nice_dev


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:

  1. Now checks for dead links or loops and marks them.
  2. Added more prepare code, so order is now ignorable
like image 25
Foobar Avatar answered Sep 19 '25 05:09

Foobar