Array Key Reorganizing Functions

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
User avatar
McInfo
DevNet Resident
Posts: 1532
Joined: Wed Apr 01, 2009 1:31 pm

Array Key Reorganizing Functions

Post 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.
Last edited by McInfo on Thu Jun 17, 2010 2:29 pm, edited 1 time in total.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Array Key Reorganizing Functions

Post 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
User avatar
McInfo
DevNet Resident
Posts: 1532
Joined: Wed Apr 01, 2009 1:31 pm

Re: Array Key Reorganizing Functions

Post 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.
Post Reply