Page 1 of 1

Array Key Reorganizing Functions

Posted: Thu Nov 12, 2009 6:48 pm
by McInfo
I decided to revisit a problem that I asked about seven months ago that involved reorganizing the keys of a multi-dimensional array. Primarily, my goal was to modify a $_FILES array so that multiple files would be grouped by file number rather than by attribute name as in the default hierarchy. Although that is not a difficult task when the array is familiar, it is more challenging to write a flexible, abstract function that is not limited to any particular array.

In the hopes that they will be useful to anyone else, I've decided to share the pair of functions that I came up with.

If you have time to test these functions, please give me some ideas for what needs improvement.

Code: Select all

<?php
/**
 * Analyzes an array and generates for each non-array element a list of the keys
 * used to navigate the hierarchy to get to that element.<br />
 *
 * @param array $input
 * The array to trace should be uniform (have equal depth on all branches).
 *
 * @param array $output
 * This by-reference, three-dimensional array describes all non-array elements
 * of the input array.<br />
 * <br />
 * Structure of the resultant $output array:<br />
 * <ul>
 *   <li>[0] First non-array element in order of discovery:
 *     <ul>
 *       <li>[0] Element content</li>
 *       <li>[1] List of ancestor keys:
 *         <ul>
 *           <li>[0] Root ancestor key</li>
 *           <li>...</li>
 *           <li>[n-1] Grand-parent key</li>
 *           <li>[n] Parent key</li>
 *         </ul>
 *       </li>
 *     </ul>
 *   </li>
 *   <li>[1] Second non-array element:
 *     <ul>
 *       <li>[0] Element content</li>
 *       <li>[1] List of ancestor keys:
 *         <ul>
 *           <li>[0] Root ancestor key</li>
 *           <li>...</li>
 *           <li>[n-1] Grand-parent key</li>
 *           <li>[n] Parent key</li>
 *         </ul>
 *       </li>
 *     </ul>
 *   </li>
 *   <li>...</li>
 * </ul>
 *
 * @return array
 * The returned array is always empty (except for recursive calls).
 */
function traceArray (array $input, array &$output) {
    $trace = (func_num_args() > 2) ? func_get_arg(2) : array();
    if (! is_array($trace)) {
        $trace = array();
    }
    foreach ($input as $n => $node) {
        $trace[] = $n;
        if (is_array($node)) {
            $self = __FUNCTION__;
            $trace = $self($node, $output, $trace); // Recursive call
        } else {
            $output[] = array($node, $trace);
            array_pop($trace);
        }
    }
    array_pop($trace);
    return $trace;
}
 
/**
 * Arranges the hierarchy of keys in a multidimensional array according to the
 * sequence of integers in the $order array.<br />
 *
 * @param array $input
 * The array to rekey should be uniform (have equal depth on all branches).
 *
 * @param array $order
 * This one-dimensional array of integers determines the new key sequence. The
 * maximum integer in this array should be less than the depth of the shallowest
 * non-array element in the $input array. The number of elements in this array
 * should be less than or equal to that depth.
 *
 * @return array
 * Returns the rekeyed array or an empty array if $order is invalid.
 */
function rekeyArray (array $input, array $order) {
    $output = array();
    $order = preg_grep('/^\d+$/', $order); // Prevents "eval" code injection
    if (count($order) == 0) {
        return $output;
    }
    $trace = array();
    traceArray($input, $trace);
    $count = count($trace);
    for ($t = 0; $t < $count; $t++) {
        $keys = '[ $trace[$t][1]['.implode('] ][ $trace[$t][1][', $order).'] ]';
        eval('$output'.$keys.' = $trace[$t][0];');
    }
    return $output;
}
Example usage:

Code: Select all

$files = array
(   'images' => array
    (   'name' => array
        (   0 => 'a.png'
        ,   1 => 'b.gif'
        )
    ,   'type' => array
        (   0 => 'image/png'
        ,   1 => 'image/gif'
        )
    ,   'tmp_name' => array
        (   0 => '/tmp/php0001.tmp'
        ,   1 => '/tmp/php0002.tmp'
        )
    ,   'error' => array
        (   0 => 0
        ,   1 => 0
        )
    ,   'size' => array
        (   0 => 100
        ,   1 => 200
        )
    )
);
print_r(rekeyArray($files, array(2, 1)));
/*
Array
(
    [0] => Array
        (
            [name] => a.png
            [type] => image/png
            [tmp_name] => /tmp/php0001.tmp
            [error] => 0
            [size] => 100
        )
     [1] => Array
        (
            [name] => b.gif
            [type] => image/gif
            [tmp_name] => /tmp/php0002.tmp
            [error] => 0
            [size] => 200
        )
)
*/
Edit: This post was recovered from search engine cache.

Re: Array Key Reorganizing Functions

Posted: Sun Nov 15, 2009 10:10 pm
by josh
Interesting. One way to improve it would be to wrap the functions in a class (even if they are just static methods). Also maybe a non eval version would probably appease certain developers, as well as make it more portable.

This is actually pretty useful, is it free to use (as in speech)? :D

Re: Array Key Reorganizing Functions

Posted: Mon Nov 16, 2009 12:21 am
by McInfo
josh wrote:[...]wrap the functions in a class[...]
I did think about making the functions part of a class because rekeyArray() is dependent on traceArray() and traceArray() would be able to return the $output array if $output was outside the function scope (strange but true). If I were making an Array class, I would certainly give it trace() and rekey() methods, but a class with just these two methods seems unnecessary to me. Then again, being encapsulated in an aggregatable class would protect an $output array that is outside the function scope...
josh wrote:[...]non eval version[...]
I would have to look into that possibility. eval() was the immediate choice given that the array has an unknown number of dimensions and can have a practically unlimited number of dimensions. A non-eval() solution would probably involve recursion.
josh wrote:[...]free to use[...]
Now that the code is in the wild, I can't stop anyone from using it as they please. I guess that implies that it is free to use or, more correctly, everyone is free to use it.

Edit: This post was recovered from search engine cache.