Kyle
Kyle

Reputation: 405

Group integer values in a flat array by non-integer values to create a 3-level array

I'm trying to take data that is received from form input and, in PHP, change the way it is ordered for use in a template creation module/app. I need it to be in a multidimensional array for use.

How the array currently looks

$templateData = [
    0 => 'h',
    1 => 1,
    2 => 2,
    3 => 3,
    4 => 'c-1-3',
    5 => 3,
    6 => 5,
    7 => 'c-2-3',
    8 => 3,
    9 => 'c-3-3',
    10 => 'f',
    11 => 2
];

How the array should look

$templateData = [
    0 =>[ //header row
        0 => [1,2,3] //column
    ],
    1 =>[ //content row(s)
        0 => [3,5], //column
        1 => [3],
        2 => [0]
    ],
    2 => [ //footer row
        0 => [2] //column
    ]
]

The 'h' represents the start of the header row of the template and 'c-1-3' represents a the start of a row with 3 columns starting with the first column. The 'f' represents the start of the footer row. I'm just drawing a blank right now and I can't wrap my head around it.

Here is where I'm at right now, but it's still not working as intended:

$elements = $request->input('elements'); //laravel code that grabs array input
$row = 0;
$column = 0;
foreach($elements as $key => $element) {
    //if value is a letter
    if(preg_match('/^[a-zA-Z]/', $element)) {
        //if the 3rd letter in the string is a 1
        if(!isset($element[2]) || $element[2] == '1') { $row++; }
        //cycle through values after current key
        for($i=$key+1; $i < count($elements);$i++){
            //until you hit another letter
            if(preg_match('/^[a-zA-Z]/', $elements[$i])) { break; }
            $column++;
            $temp[$row][$column][] = $elements[$i];
        }
        $column = 0;
    }
}

The $templateData and $elements variables are the same in the the above and below context.

Upvotes: 0

Views: 118

Answers (3)

mickmackusa
mickmackusa

Reputation: 48100

You won't need to use arithmetic to appropriately target the indexes of the multidimensional result array if you push data into reference variables.

The first level keys will be dictated by the leading non-hyphen substring of non-integer values.

The second level keys will be dictated by the whole non-integer value. This provides accurate grouping.

Code: (Demo)

$result = [];
foreach ($templateData as $v) {
    if (!is_int($v)) {
        if (isset($k2) && empty($ref2[$k2])) {
            $ref2[$k2][] = 0;  // set default 0 if previous group received no values
        }
        $k2 = $v;  // cache the level 2 value
        $k1 = strtok($v, '-');  // cache the level 1 value
        if (!isset($ref1[$k1])) {
            $ref1[$k1] = [];
            $result[] =& $ref1[$k1]; // push level 1 reference into result array
        }
        if (!isset($ref2[$k2])) {
            $ref2[$k2] = [];
            $ref1[$k1][] =& $ref2[$k2];  // push level 2 reference into level 1 reference
        }
    } elseif (isset($k2)) {
        $ref2[$k2][] = $v; // push integer into level 2 reference
    }
}
var_export($result);

Output:

array (
  0 => 
  array (
    0 => 
    array (
      0 => 1,
      1 => 2,
      2 => 3,
    ),
  ),
  1 => 
  array (
    0 => 
    array (
      0 => 3,
      1 => 5,
    ),
    1 => 
    array (
      0 => 3,
    ),
    2 => 
    array (
      0 => 0,
    ),
  ),
  2 => 
  array (
    0 => 
    array (
      0 => 2,
    ),
  ),
)

Upvotes: -2

Kyle
Kyle

Reputation: 405

This is the code I ended up with. Finally got it. This works:

    //grab input
    $elements = $request->input('elements');

    //initiate row/column numbers
    $row = 0;
    $column = 0;
    $templateData = array();

    //loop through each array value
    foreach($elements as $key => $element) {

        //if value starts with a letter
        if(preg_match('/^[a-z]/', $element)){

            //set total columns
            if($element != 'h' && $element != 'f')
            {
                $totalColumns = substr($element, 4);
            } else {
                $column = 1;
                $totalColumns = 1;
            }

            //if h, c-1-*, or f is detected advance the row
            if(substr($elements[$key], 0) == 'h' || substr($elements[$key], 2) == 1 || substr($elements[$key], 0) == 'f')
            {
                $row++;
            }

            //loop through keys following first recognized letter until you hit another letter
            for($i = $key+1; $i < count($elements) && !preg_match('/^[a-z]/',$elements[$i]); $i++)
            {
                //add element id to column
                $templateData[$row-1][$column-1][] = $elements[$i];
            }

            //if first value in column is not set, add a 0 value to column
            if(!isset($templateData[$row-1][$column-1][0]))
            {
                $templateData[$row-1][$column-1][0] = 0;
            }

            //if its the last column in the set, set to 0
            if($column == $totalColumns)
            {
                $column = 0;
            }

            $column++;
        }
    }
    dd($templateData);

Upvotes: 1

Jorge Faianca
Jorge Faianca

Reputation: 791

It's a bit overkill but you can have full power, and you just need to change the handlers if you want to change the logic. Hope it can help somehow.

$templateData = [
    0 => 'h',
    1 => 1,
    2 => 2,
    3 => 3,
    4 => 'c-1-3',
    5 => 3,
    6 => 5,
    7 => 'c-2-3',
    8 => 3,
    9 => 'f',
    10 => 2
];

$tmpKey = '';
$tmpArray = [];

class Test
{
    /**
     * @var []
     */
    private $data = [];

    /**
     * @var []
     */
    private $columns = [];

    /**
     * @var array
     */
    private $numOfColumns = 0;

    /**
     * @var []
     */
    private $header = [];

    /**
     * @var []
     */
    private $footer = [];

    /**
     * @var string
     */
    private $current = [
        'key' => '',
        'matches' => ''
    ];

    /**
     * @var []
     */
    private $rules = [
        'header' => [
            'expression' => 'h',
            'handler' => 'headerHandler',
            'matches' => false
        ],
        'footer' => [
            'expression' => 'f',
            'handler' => 'footerHandler',
            'matches' => false
        ],
        'columns' => [
            'expression' => 'c-\d+-\d+',
            'handler' => 'columnHandler',
            'matches' => "columnMatches"
        ]
    ];

    /**
     * @param $data
     */
    public function __construct($data)
    {
        $this->data = $data;
        $this->process();
    }

    /**
     * @throws Exception
     */
    private function process()
    {
        foreach($this->data as $data) {

            if (!is_int($data)) {
                if (!$this->setRule($data)) {
                    throw new \RuntimeException("Rule Not found.");
                }
            } else {
                $this->handleRule($data);
            }
        }
    }

    /**
     * @param $data
     */
    private function headerHandler($data)
    {
        $this->header[] = $data;
    }

    /**
     * @param $data
     */
    private function footerHandler($data)
    {
        $this->footer[] = $data;
    }

    /**
     * @param $data
     */
    private function columnHandler($data)
    {
        $columns = explode('-', $this->current['matches']);
        if ($this->columns[($columns[1] - 1)][0] == 0){
            $this->columns[($columns[1] - 1)][0] = $data;
        } else {
            array_push($this->columns[($columns[1] - 1)], $data);
        }
    }

    /**
     * @param $data
     */
    private function columnMatches($data)
    {
        $columns = explode('-', $data);

        if ($this->numOfColumns == 0) {
            $this->numOfColumns = end($columns);
            $this->columns = array_fill(0, $this->numOfColumns, [0]);
        }
    }

    /**
     * @param $data
     * @return bool
     */
    private function setRule($data)
    {
        foreach($this->rules as $key => $rules) {
            if (preg_match("/^{$rules['expression']}$/", $data, $matches)) {
                if ($rules['matches']) {
                    $this->{$rules['matches']}($data);
                }
                $this->current['key'] = $key;
                $this->current['matches'] = $data;
                return true;
            }
        }

        return false;
    }

    /**
     * @return mixed
     */
    public function getHeader()
    {
        return $this->header;
    }

    /**
     * @return mixed
     */
    public function getColumns()
    {
        return $this->columns;
    }

    /**
     * @return mixed
     */
    public function getFooter()
    {
        return $this->footer;
    }

    /**
     * @return array
     */
    public function getResult()
    {
        return [
            0 => [$this->header],
            1 => $this->columns,
            2 => $this->footer
        ];
    }

    /**
     * @param $data
     */
    private function handleRule($data)
    {
        $this->{$this->rules[$this->current['key']]['handler']}($data);
    }

}

$test = new Test($templateData);
echo '<h1>Header</h1>';
var_dump($test->getHeader());
echo '<h1>Footer</h1>';
var_dump($test->getFooter());
echo '<h1>Columns</h1>';
var_dump($test->getColumns());
echo '<h1>Result</h1>';
var_dump($test->getResult());exit;

Ideone: Example Here

Upvotes: 0

Related Questions