Skip to content

Instantly share code, notes, and snippets.

@Jeff-Russ
Last active July 17, 2022 04:42
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jeff-Russ/da1d19592c1dcf71d0f1d01a93499e37 to your computer and use it in GitHub Desktop.
Save Jeff-Russ/da1d19592c1dcf71d0f1d01a93499e37 to your computer and use it in GitHub Desktop.
PHP: Sub-Arrays: Rotating, Sorting, Categorizing, Flattening, and Merging

SUB-ARRAY FUNCTIONS 1: ROTATING

ISSUE: you want the value of 'subkey' below to be the keys of the array. Also (although might not tell simply by looking) the current keys are error codes and you want to rotate them into the subarray elements under the key 'code', replacing the 'subkey' element which will not be the outer-most keys.

$by_code = array(
  404 => ['subkey'=>'subkey-val-DOH','subkey2'=>'subkey2-val-1'],
  302 => ['subkey'=>'subkey-val-one','subkey2'=>'subkey2-val-2'],
  418 => ['subkey'=>'subkey-val-DOH','subkey2'=>'subkey2-val-3'],
  422 => ['subkey'=>'subkey-val-two','subkey2'=>'subkey2-val-4'],
);
function pretty_print($a){echo json_encode($a,JSON_PRETTY_PRINT)."\n<hr>\n";}

array_rotate()

array_rotate() is like array_flip() only it's for Two-dimensional arrays. There can be more than two dimensions but the value of $subkey can't be another array and must be a valid key.

pretty_print( array_rotate($by_code, 'subkey', 'code') );

output:

[
  "subkey-val-DOH"=>[ "subkey2"=>"subkey2-val-3", "code"=>404 ],
  "subkey-val-one"=>[ "subkey2"=>"subkey2-val-2", "code"=>302 ]
  "subkey-val-two"=>[ "subkey2"=>"subkey2-val-4", "code"=>422 ],
]

array_rotate_category()

array_rotate_category() is like array_rotate() only it avoids clobbering duplicate values of $subkey by creating "category" array containters for each possible value of $subkey. This additional array container will be created even if there is only one sub-key with a particular value (and would not have caused a collision).

pretty_print( array_rotate_category($by_code, 'subkey', 'code') );

output:

[
  "subkey-val-DOH"=>[
    0=>[ "subkey2"=>"subkey2-val-1", "code"=>404 ],
    1=>[ "subkey2"=>"subkey2-val-3", "code"=>404 ]
  ],
  "subkey-val-one"=>[
    0=>[ "subkey2"=>"subkey2-val-2", "code"=>302 ]
  ],
  "subkey-val-two"=>[
    0=>[ "subkey2"=>"subkey2-val-4", "code"=>422 ]
  ]
]

array_rotate_jagged()

array_rotate_jagged() is an adaptive version of array_rotate_category() meaning it will create a category only if a collision would occur. This means that the final array returned is "jagged" meaning each element might have a diffent dimensional depth (could be a single array or an array of arrays).

pretty_print( array_rotate_jagged($by_code, 'subkey', 'code') ); 

output:

[
  "subkey-val-DOH"=>[
    0=>[ "subkey2"=>"subkey2-val-1", "code"=>404 ]
    1=>[ "subkey2"=>"subkey2-val-3", "code"=>418 ]
  ],
  "subkey-val-one"=>[ "subkey2"=>"subkey2-val-2", "code"=>302 ],
  "subkey-val-two"=>[ "subkey2"=>"subkey2-val-4", "code"=>422 ]
]

The following three methods apply similar processes seen in array_rotate(), array_rotate_category(), and array_rotate_jagged() but do so for every inner key as well as the outermost key.

The returned array is an array where each key is the result of a search for each subkey, only showing the LAST result.

create_rotations()

create_rotations() is the en masse version of array_rotate().

pretty_print( create_rotations($by_code, 'code') );

output:

[
    "code"=>[
        404=>[ "subkey"=>"subkey-val-DOH", "subkey2"=>"subkey2-val-1" ],
        302=>[ "subkey"=>"subkey-val-one", "subkey2"=>"subkey2-val-2" ],
        418=>[ "subkey"=>"subkey-val-DOH", "subkey2"=>"subkey2-val-3" ],
        422=>[ "subkey"=>"subkey-val-two", "subkey2"=>"subkey2-val-4" ]
    ],
    "subkey"=>[
        "subkey-val-DOH"=>[ "subkey2"=>"subkey2-val-3", "code"=>404 ],
        "subkey-val-one"=>[ "subkey2"=>"subkey2-val-2", "code"=>302 ],
        "subkey-val-two"=>[ "subkey2"=>"subkey2-val-4", "code"=>422 ]
    ],
    "subkey2"=>[
        "subkey2-val-1"=>[ "subkey"=>"subkey-val-DOH", "code"=>404 ],
        "subkey2-val-2"=>[ "subkey"=>"subkey-val-one", "code"=>302 ],
        "subkey2-val-3"=>[ "subkey"=>"subkey-val-DOH", "code"=>418 ],
        "subkey2-val-4"=>[ "subkey"=>"subkey-val-two", "code"=>422 ]
    ]
]

SUB-ARRAY FUNCTIONS 2: SORTING

create_sorts()

create_sorts() is the en masse version of array_rotate_category().

NOTE: The primary key (if provided) is still "array-ified" when the lookup table sort is created for it EVEN though collisions are not possible. This is so you have a universal way of dealing with lookups (you will always the same depths of arrays.) In other words even thought there should be only one "result" if you search by primary key, you will get an array with one value.

pretty_print( create_sorts($by_code, 'code') );

output:

[
    "code"=>[
        404=>[
            0=>[ "subkey"=>"subkey-val-DOH", "subkey2"=>"subkey2-val-1" ]
        ],
        302=>[
            0=>[ "subkey"=>"subkey-val-one", "subkey2"=>"subkey2-val-2" ]
        ],
        418=>[
            0=>[ "subkey"=>"subkey-val-DOH", "subkey2"=>"subkey2-val-3" ]
        ],
        422=>[
            0=>[ "subkey"=>"subkey-val-two", "subkey2"=>"subkey2-val-4" ]
        ]
    ],
    "subkey"=>[
        "subkey-val-DOH"=>[
            0=>[ "subkey2"=>"subkey2-val-1", "code"=>404 ],
            1=>[ "subkey2"=>"subkey2-val-3", "code"=>418 ]
        ],
        "subkey-val-one"=>[
            0=>[ "subkey2"=>"subkey2-val-2", "code"=>302 ]
        ],
        "subkey-val-two"=>[
            0=>[ "subkey2"=>"subkey2-val-4", "code"=>422 ]
        ]
    ],
    "subkey2"=>[
        "subkey2-val-1"=>[
            0=>[ "subkey"=>"subkey-val-DOH", "code"=>404 ]
        ],
        "subkey2-val-2"=>[
            0=>[ "subkey"=>"subkey-val-one", "code"=>302 ]
        ],
        "subkey2-val-3"=>[
            0=>[ "subkey"=>"subkey-val-DOH", "code"=>418 ]
        ],
        "subkey2-val-4"=>[
            0=>[ "subkey"=>"subkey-val-two", "code"=>422 ]
        ]
    ]
]

create_sorts_jagged()

create_sorts_jagged() is the en masse version of array_rotate_jagged().

pretty_print( create_sorts_jagged($by_code, 'code') ); 

output:

[
    "code"=>[
        404=>[ "subkey"=>"subkey-val-DOH", "subkey2"=>"subkey2-val-1" ],
        302=>[ "subkey"=>"subkey-val-one", "subkey2"=>"subkey2-val-2" ],
        418=>[ "subkey"=>"subkey-val-DOH", "subkey2"=>"subkey2-val-3" ],
        422=>[ "subkey"=>"subkey-val-two", "subkey2"=>"subkey2-val-4" ]
    ],
    "subkey"=>[
        "subkey-val-DOH"=>[
            0=>[ "subkey2"=>"subkey2-val-1", "code"=>404 ],
            1=>[ "subkey2"=>"subkey2-val-3", "code"=>418 ]
        ],
        "subkey-val-one"=>[ "subkey2"=>"subkey2-val-2", "code"=>302 ],
        "subkey-val-two"=>[ "subkey2"=>"subkey2-val-4", "code"=>422 ]
    ],
    "subkey2"=>[
        "subkey2-val-1"=>[ "subkey"=>"subkey-val-DOH", "code"=>404 ],
        "subkey2-val-2"=>[ "subkey"=>"subkey-val-one", "code"=>302 ],
        "subkey2-val-3"=>[ "subkey"=>"subkey-val-DOH", "code"=>418 ],
        "subkey2-val-4"=>[ "subkey"=>"subkey-val-two", "code"=>422 ]
    ]
]

create_sorts_jagged()

create_sorts_jagged() is the en masse version of array_rotate_jagged().

pretty_print( subarray_search($by_code, 'subkey', 'subkey-val-DOH') );

output:

[
    404,
    418
]

create_sorts_jagged()

create_sorts_jagged() is the en masse version of array_rotate_jagged().

echo subarray_find($by_code, 'subkey', 'subkey-val-DOH');

output:

404

SUB-ARRAY FUNCTIONS 3: FLATTENING AND MERGING

$argv = [
    'arg0 value',
    'arg1 value',
    'arg2 value',
    [   'arg3[0] key' =>'arg3[0] value',
        'arg3[1] key' =>'arg3[1] value',
    ],
    'arg4 value',
    [   'arg5[0] value',
        'arg5[1] key'=>'arg5[1] value'
    ]
];

array_flatten()

array_flatten() makes a two-dimensional into one-dimension while retaining sequential order. Both the top and second level arrays have their integer keys re-indexed to in consecutive sequence.

example using $argv above:

$arr = array_flatten($argv, true, false);
echo json_encode($arr, JSON_PRETTY_PRINT);

output:

{
    "0": "arg0 value",
    "1": "arg1 value",
    "2": "arg2 value",
    "arg3[0] key": "arg3[0] value",
    "arg3[1] key": "arg3[1] value",
    "4": "arg4 value",
    "5": "arg5[0] value",
    "arg5[1] key": "arg5[1] value"
}

If argument 2 $safe is set to true, an array will not be returned if duplicate string keys are found. If argument 2 $fatal is false, false will be returned in this case. If it is not false a fatal error will occur. If fatal is set to a string, it will be used as the label for the error message.

merge_subarrays()

merge_subarrays() removes sub-arrays from an array and merges them together into another array. The return value is an array of the two arrays with they keys 'top' and 'sub'. The contents of 'top' are not re-indexed and there may be gaps in integer keys.

example using $argv above:

$arr = merge_subarrays($argv, true, false);
echo json_encode($arr, JSON_PRETTY_PRINT);

output:

{
    "top": {
        "0": "arg0 value",
        "1": "arg1 value",
        "2": "arg2 value",
        "4": "arg4 value"
    },
    "sub": {
        "arg3[0] key": "arg3[0] value",
        "arg3[1] key": "arg3[1] value",
        "0": "arg5[0] value",
        "arg5[1] key": "arg5[1] value"
    }
}

If second argument $safe is set to true, duplicate keys cannot be overwritten and instead, false is returned.

argv_and_vars()

Similar to both func_get_args() and merge_subarrays() only not needing the first argument as it is the caller's arguments (from a function or method). The return is the same as merge_subarrays() only 'top' is called 'argv' and 'sub' is called 'vars'

An example:

function test() {
    extract(argv_and_vars());
    echo "argv: ".json_encode($argv, JSON_PRETTY_PRINT);
    echo "vars: ".json_encode($vars, JSON_PRETTY_PRINT);
    extract($vars);
    echo '$var1 '.$var1."\n";
    echo '$var2 '.$var2."\n";
    echo '$var3 '.$var3."\n";
}

$named_args = array('var1'=>'var1 value', 'var2' =>'var2 value');
$one_more = array('var3'=>'var3 value');
test('arg0','arg1', 'arg2', $named_args, 'arg4', $one_more);

output:

argv: {
    "0": "arg0",
    "1": "arg1",
    "2": "arg2",
    "4": "arg4"
}
vars: {
    "var1": "var1 value",
    "var2": "var2 value",
    "var3": "var3 value"
}
$var1 var1 value
$var2 var2 value
$var3 var3 value
<?php namespace Jr;
##### GETTING FIRST OR LAST MATCH ONLY
# Find key of subarray that has a specified key with a specified value
function subarray_find ($arr, $subkey, $subval, $get_last=false) {
if ($get_last) $arr = array_reverse($arr, true);
foreach ($arr as $key=>$arr) {
foreach ($arr as $k=>$v) {
if ($k===$subkey && $v===$subval) return $key;
}
}
}
# Move subkey out of each subarray to be key of entire subarray
# and move key of entire subarray in to subarray at $oldkey If more than
# one subarray has same value first match overwrites unless $get_last
function array_rotate ($arr, $subkey, $oldkey=null, $get_last=false) {
$new_arr = [];
foreach ($arr as $k=>$sub_arr) {
$categ = $sub_arr[$subkey];
if (!$get_last && isset($new_arr[$categ])) continue;
unset($sub_arr[$subkey]);
if ($oldkey===null) $sub_arr[] = $k;
else $sub_arr[$oldkey] = $k;
$new_arr[$categ] = $sub_arr;
}
return $new_arr;
}
# Create rotations for each subkey. In other words, apply array_rotate() to all
function create_rotations ($arr, $prim_key_name=null, $get_last=false) {
if ($prim_key_name===null) $sorts = [];
else $sorts = [ $prim_key_name => $arr ];
foreach ($arr as $pkey=>$record) {
$record_w_pkey = $record;
$record_w_pkey[$prim_key_name] = $pkey;
foreach ($record as $field=>$value) {
${"sorted_by_$field"} = $record_w_pkey;
unset(${"sorted_by_$field"}[$field]);
if (!$get_last && isset($sorts[$field][$value])) continue;
$sorts[$field][$value] = ${"sorted_by_$field"};
}
}
return $sorts;
}
# Find keys of all subarrays that have a specified key with a specified value
function subarray_search ($arr, $subkey, $subval) {
foreach ($arr as $key=>$arr) {
foreach ($arr as $k=>$v) {
if ($k===$subkey) { if ($v===$subval) $found[] = $key; break; }
}
}
return $found;
}
# Just like array_rotate() but with arrays for each subkey so no overwriting
function array_rotate_category ($arr, $subkey, $oldkey=null) {
$new_arr = [];
foreach ($arr as $k=>$sub_arr) {
$categ = $sub_arr[$subkey];
unset($sub_arr[$subkey]);
if ($oldkey===null) $sub_arr[] = $k;
else $sub_arr[$oldkey] = $k;
$new_arr[$categ][] = $sub_arr;
}
return $new_arr;
}
# Create rotations for each subkey. In other words,
# apply array_rotate_category() to all
function create_sorts ($arr, $prim_key_name=null) {
if ($prim_key_name===null) $sorts = [];
else {
$sorts = [ $prim_key_name => [] ];
foreach ($arr as $k=>$v) $sorts[$prim_key_name][] = [ $k=>$v ];
}
foreach ($arr as $pkey=>$record) {
$record_w_pkey = $record;
$record_w_pkey[$prim_key_name] = $pkey;
foreach ($record as $field=>$value) {
${"sorted_by_$field"} = $record_w_pkey;
unset(${"sorted_by_$field"}[$field]);
$sorts[$field][$value][] = ${"sorted_by_$field"};
}
}
return $sorts;
}
# Just like array_rotate() but with arrays if overwriting would occur.
function array_rotate_jagged ($arr, $subkey, $oldkey=null) {
$new_arr = [];
foreach ($arr as $k=>$sub_arr) {
$categ = $sub_arr[$subkey];
unset($sub_arr[$subkey]);
if ($oldkey===null) $sub_arr[] = $k;
else $sub_arr[$oldkey] = $k;
if (!isset($new_arr[$categ])) $new_arr[$categ] = $sub_arr;
else {
$new_arr[$categ] = array($new_arr[$categ]);
$new_arr[$categ][] = $sub_arr;
}
}
return $new_arr;
}
# Create rotations for each subkey. In other words,
# apply array_rotate_jagged() to all
function create_sorts_jagged ($arr, $prim_key_name=null) {
if ($prim_key_name===null) $sorts = [];
else $sorts = [ $prim_key_name => $arr ];
foreach ($arr as $pkey=>$record) {
$record_w_pkey = $record;
$record_w_pkey[$prim_key_name] = $pkey;
foreach ($record as $field=>$value) {
${"sorted_by_$field"} = $record_w_pkey;
unset(${"sorted_by_$field"}[$field]);
if (isset($sorts[$field][$value])) {
$sorts[$field][$value] = [ $sorts[$field][$value] ];
$sorts[$field][$value][] = ${"sorted_by_$field"};
} else { $sorts[$field][$value] = ${"sorted_by_$field"}; }
}
}
return $sorts;
}
##### FLATTEN & MERGE
# Make two-dimensional into one-dimension while retaining sequencial order.
# Both the top and second level arrays have their integer keys are re-indexed.
# $fatal can be boolean or a string to provide error label
function array_flatten($arr, $safe=true, $fatal=false) {
$ret = array();
if (!$safe) {
foreach ($arr as $key=>$val) {
if (!is_array($val)) $ret[$key] = $val;
else $ret = array_merge($ret, $val);
}
} elseif ($fatal===false) { $count = 0;
foreach ($arr as $key=>$val) {
if (!is_array($val)) { $ret[$key] = $val; $count++; }
else { $count += count($val);
foreach ($val as $k=>$v) {
if (!is_string($k)) $ret[] = $v; else $ret[$k] = $v;
}
}
}
if (count($ret)!==$count) return false;
} else {
foreach ($arr as $key=>$val) {
if (!is_array($val)) $ret[$key] = $val;
else {
foreach ($val as $k=>$v) {
if (!is_string($k)) $ret[] = $v;
elseif (!array_key_exists($k, $ret)) $ret[$k] = $v;
else { $label = is_string($fatal)&&$fatal!==''? "$fatal: ":'';
trigger_error($label."Duplicate key: '$k'", E_USER_ERROR);
}
}
}
}
}
return $ret;
}
# Remove sub-arrays and merges them together into another array ('sub').
# The return value ['top'=>[...], 'sub'=>[...]]
function merge_subarrays($arr, $safe=true) {
$ret = array( 'top'=>$arr, 'sub'=>array() );
$count = 0;
foreach ($ret['top'] as $k=>$v) {
if (is_array($v)) {
unset( $ret['top'][$k] );
$ret['sub'][] = $v;
$count += count($v);
}
}
if ($count!==0) {
$ret['sub'] = call_user_func_array('array_merge', $ret['sub']);
if ($safe && count($ret['sub'])!==$count) return false;
}
return $ret;
}
# Similar to both func_get_args() and merge_subarrays() only not needing the
# first argument as it is the caller's arguments (from a function or method).
# The return is the same as merge_subarrays() only:
# 'top' is called 'argv' and 'sub' is called 'vars'
function argv_and_vars($safe=true) {
$trace = debug_backtrace(false, 2);
$ret = array( 'argv'=>$trace[1]['args'], 'vars'=>array() );
$count = 0;
foreach ($ret['argv'] as $k=>$v) {
if (is_array($v)) {
unset( $ret['argv'][$k] );
$ret['vars'][] = $v;
$count += count($v);
}
}
if ($count!==0) {
$ret['vars'] = call_user_func_array('array_merge', $ret['vars']);
if ($safe && count($ret['vars'])!==$count) return false;
}
return $ret;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment