Home > Mobile >  Recursive array processing
Recursive array processing

Time:12-14

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 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'] = getLeafNodeValue($matches[1], $data);
  }
  return $data[$id]['data'];
}

print_r($data);

Online Demo

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.

  • Related