Member Avatar for diafol

Hi all. Have been racking my brains about how to go about creating a multidimensional array dynamically from an array of keys. Something like...

$keys = array('key1', 'key2', 'key3');
$value = 'some value';

// $md = ...???!!!... 

I need the $md multidimensional array to have the keys thus...

$md['key1']['key2']['key3'] = $value;

Obviously that could be hardcoded quite easily, but my problem it that the $keys array is variable - it can hold different values and the number of items can vary. So how to build an md array? I'm looking for a generic routine.

I've come close a few times, but it's just beyond me. Any clues would be appreciated.

works:

$keys = array('key1', 'key2', 'key3');
$value = 'some value';
$string='$md';
foreach($keys as $index => $key)
    {   
    $string.="['$key']";
    }
 $string.= '=$value;';
 eval($string);
 var_dump($md); 

but:

If eval() is the answer, you're almost certainly asking the
wrong question. -- Rasmus Lerdorf, BDFL of PHP

Member Avatar for diafol

Yes I agree with Rasmus. Thanks anyway pzuurveen - I did consider it (eval), but then thought better of it as I wouldn't be in control of the keys, so couldn't whitelist them. I suppose I could make them "safe", but I really don't like evil eval :)

better:

function makeArray($keys, $value)
        {
        $var=array();   
        $index=array_shift($keys);
        if (!isset($keys[0]))
            {
            $var[$index]=$value;
            }
        else 
            {   
            $var[$index]=makeArray($keys,$value);            
            }
        return $var;
        }

$keys = array('key1', 'key2', 'key3');
$value = 'some value';   
$md =makeArray($keys,$value);
var_dump($md);
Member Avatar for diafol

That looks promising. This is part of a bigger method, so I've still got things to work on. I'll get back. Thank you.

Just a different approach since it is an interesting question

$keys = array('key1', 'key2', 'key3');
$value = 'some value';

$md = array();
$md[$keys[count($keys)-1]] = $value;
for($i=count($keys)-2; $i>-1; $i--)
{
  $md[$keys[$i]] = $md;
  unset($md[$keys[$i+1]]);
}
var_dump($md);
Member Avatar for diafol

Interesting. Iteration instead of recursion. I tried a similar approach with reversing the keys with array_reverse and setting the values on the first iteration and building up - a similar approach. Plenty to play with now :) Thank you.

FYI: I'm also playing with creating standard objects and building those. Multiple approaches heh, heh.

I'll lay my cards on the table as there's some interest - this was initiated by a call for help on a thread where the user needed to create rowspans for a html table:

http://www.daniweb.com/web-development/php/threads/476316/create-a-table-for-databse-results-with-rowspan/2#post2082526

So (I hope Webville doesn't mind) - here's some sample data:

$keys = array("date","seller","product","qty","amount","tax");

$values = array(
    array('2014-10-04', 'julius',   'chocolate',101,    928347,     2145),
    array('2014-10-04', 'julius',   'tea',      120,    367482,     4152),
    array('2014-10-04', 'julius',   'coffee',   160,    275896,     6895),
    array('2014-10-04', 'ben',      'brandy',   104,    836578,     7485),
    array('2014-10-04', 'ben',      'whisky',   108,    927546,     7485),
    array('2014-10-04', 'joe',      'vodka',    134,    254657,     1425),
    array('2014-10-03', 'julius',   'heineken', 200,    973647,     2301),
    array('2014-10-03', 'julius',   'vodka',    300,    293874,     4152),
    array('2014-10-03', 'julius',   'tea',      400,    103984,     1298),
    array('2014-10-03', 'simon',    'mint tea', 500,    920384,     1023),
    array('2014-10-02', 'simon',    'coffee',   550,    756473,     7845),
    array('2014-10-02', 'julius',   'tea',      700,    263784,     1526),
    array('2014-10-02', 'julius',   'herbal',   110,    294375,     9861),
    array('2014-10-02', 'julius',   'port',     111,    587848,     1458),
    array('2014-10-02', 'mags',     'sherry',   134,    784556,     4751),
    array('2014-10-02', 'mags',     'tea',      456,    125045,     2468),
    array('2014-10-01', 'ben',      'ouzo',     789,    568412,     1022),
    array('2014-10-01', 'ben',      'gin',      234,    124789,     1365),
    array('2014-10-01', 'ben',      'vodka',    547,    302658,     4758),
    array('2014-10-01', 'ben',      'coffee',   198,    865237,     3657),
);

$dbArray = array();

foreach($values as $row) $dbArray[] = array_combine($keys,$row);

That's just some random stuff that simulates and array of rows from a db call.

$groupArray = array('date','seller');

This is an array of the 'group by' fields that will have a rowspan property in a html table if there are more than on of them in the 'cascade'.

Hardcoding a solution to this particular scenario is pretty straightforward, but I'm trying to create a generic class for doing this.

Imagine a situation for creating a simple ajaxified "rearrange columns" and "sorting by". Anyway...

class rowSpanner
{
    public function __construct($dbArray,$groupArray)
    {
        $main = array();

        //$groups = array_reverse($groupArray); //for iteration method
        $gKeys = array_flip($groupArray);

        foreach($flatArray as $row)
        {
            //get non-grouped cols - ie. filter out grouped cols
            $residue = array_diff_key($row, $gKeys);
            /*e.g.
            [product] => 'coffee'
            [qty] => '198'
            [amount] => '865237'
            [tax] => '3657'
            */

            //get values of grouped keys - 
            //these will serve as nested keys themselves
            $groups = array_values(array_intersect_key($row, $gKeys));
            //e.g. [0=>'2014-10-04',1=>'julius']

            //pzuurveen method
            $tableData[] = $this->makeArray($groups, $residue);
        }


        ...iteration / recursive method here ...

    }
}

So create...

$tableData = new rowSpanner($dbArray, array('date','seller'));

It doesn't look like much, but I've got a veritable collection of methods that ended in cul-de-sacs :)

Anyway, hope you find it interesting and I'll continue to work on it too.

Member Avatar for diafol

UPDATE

Thanks to everybody who replied with great suggestions. You'll notice I went in a totally different direction, which is no reflection on the advice given - it's just that I ran into walls - a lot of them :)

Ok, for anybody still interested, I've managed to create a generic class for rowspanning groups (only about 100 lines). Almost killed me! heh heh. However, it needs the input array of data to be sorted already - which would usually be done in a DB query ORDER BY clause anyway.

So here's the class with just one public method: 'render'. I may post this in snippets after I clean it up a little - there's no commenting, no error-trapping and there's quite a lot of looping :(
So, warts and all:

class rowSpanner
{
    private $data;
    private $groupArray = array();

    public function __construct($data, $groups)
    {
        $this->data = (array) $data;
        $this->groupArray = (array) $groups;
    }

    public function render()
    {
        $flipGroups = array_flip($this->groupArray);
        $changeData = $this->get_change_data($flipGroups);
        $cascade = $this->build_cascade($changeData);

        $rowCascade = array_map('array_flip',$cascade);
        $rowspans = array_map(array($this, 'get_rowspans'), $rowCascade);   

        $output = $this->create_output($rowspans, $flipGroups);
        echo $output;
    }

    private function get_rowspans($rowCascade)  
    {
        $i=0;
        $rows = array();
        $count = count($this->data);
        foreach($rowCascade as $key=>$v)
        {
            if($i == 0)
            {
                $prevkey = $key;
                $i++;
                continue;   
            }
            $rowValue = $key - $prevkey;
            $rows[$prevkey] = $rowValue;
            $prevkey = $key;
            $i++;
        }
        $rows[$prevkey] = $count - $prevkey; 
        return $rows;
    }

    private function get_change_data($flipGroups)
    {
        $i = 0;
        $changeData = array();
        $prev = array_fill_keys($this->groupArray,'');
        foreach($this->data as $datarow)
        {
            foreach($flipGroups as $groupKey=>$groupValue)
            {
                $start = false;
                if($datarow[$groupKey] != $prev[$groupKey] || $start)
                {
                    $changeData[$groupKey][] = $i;
                    $start = true;  
                }
            }
            $i++;
            $prev = $datarow; 
        }
        return $changeData;
    }

    private function build_cascade($changeData)
    {
        $prev = array();
        $cascade = array();
        foreach($changeData as $key=>$value)
        {
            $value = array_unique(array_merge(array_values($value),array_values($prev)));
            sort($value);
            $cascade[$key] = $value;
            $prev = $value;
        }
        return $cascade;
    }



    private function create_output($rowspans, $flipGroups)
    {
        $output = '';
        foreach($this->data as $key=>$value)
        {

            $output .= '<tr>';
            $residueArray = array_diff_key($value, $flipGroups);

            foreach($this->groupArray as $groupItem)
            {
                if(isset($rowspans[$groupItem][$key]))
                {
                    $output .= '<td';
                    $output .= ($rowspans[$groupItem][$key] > 1) ? ' rowspan = "'.$rowspans[$groupItem][$key].'">' : '>';
                    $output .= $value[$groupItem];
                    $output .= '</td>';
                }   
            }
            $output .= '<td>' . implode('</td><td>', $residueArray) . '</td>';
            $output .= '</tr>';
        }
        return $output; 
    }
}

So for sample usage:

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
table{
    border-collapse: collapse;  
}

table th, table td{
    border: black 1px solid;    
}

</style>
</head>
<body>
<table>
<?php 
$x = new rowSpanner($newArray, array('date','seller'));
$x->render();?>
</table>
</body>
</html>

The constructor takes two parameters - both arrays - the first is the data array e.g. from a DB and the second is an ordered list of the columns to be rowspanned.

Here's the sample data - just simulates data array from a DB:

$keys = array("date","seller","product","qty","amount","tax");

$values = array(
    array('2014-10-04', 'julius',   'chocolate',101,    928347,     2145),
    array('2014-10-04', 'julius',   'tea',      120,    367482,     4152),
    array('2014-10-04', 'julius',   'coffee',   160,    275896,     6895),

    array('2014-10-04', 'ben',      'brandy',   104,    836578,     7485),
    array('2014-10-04', 'ben',      'whisky',   108,    927546,     7485),

    array('2014-10-04', 'joe',      'vodka',    134,    254657,     1425),



    array('2014-10-03', 'julius',   'heineken', 200,    973647,     2301),
    array('2014-10-03', 'julius',   'vodka',    300,    293874,     4152),
    array('2014-10-03', 'julius',   'tea',      400,    103984,     1298),

    array('2014-10-03', 'simon',    'mint tea', 500,    920384,     1023),



    array('2014-10-02', 'simon',    'coffee',   550,    756473,     7845),

    array('2014-10-02', 'julius',   'tea',      700,    263784,     1526),
    array('2014-10-02', 'julius',   'herbal',   110,    294375,     9861),
    array('2014-10-02', 'julius',   'port',     111,    587848,     1458),

    array('2014-10-02', 'mags',     'sherry',   134,    784556,     4751),



    array('2014-10-01', 'mags',     'tea',      456,    125045,     2468),

    array('2014-10-01', 'ben',      'ouzo',     789,    568412,     1022),
    array('2014-10-01', 'ben',      'gin',      234,    124789,     1365),



    array('2014-09-01', 'ben',      'vodka',    547,    302658,     4758),
    array('2014-09-01', 'ben',      'coffee',   198,    865237,     3657),
);

$newArray = array();

foreach($values as $row) $newArray[] = array_combine($keys,$row);

Here's a screenshot of the output, as expected:

a6375e70404625aa4487802d7c6aef3e

I admire the way you did the whole rwspan thingi [Much respect], and I had to come up with the old one, coz the deadline had already elapsed. However, I really want to understand the whole concept of how I should go about editing the code to make sure that I get more wider knowledge of how to go about this.

I can't thank you enough, but Thanx [AGAIN]...

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.