skip to Main Content

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

4

Answers


  1. 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
    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
  3. 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

    Login or Signup to reply.
  4. 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);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search