Skip to content

Instantly share code, notes, and snippets.

@cataphract
Created May 11, 2012 15:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cataphract/2660601 to your computer and use it in GitHub Desktop.
Save cataphract/2660601 to your computer and use it in GitHub Desktop.
<?php
function num_val(&$n, $keyMode)
{
$ncopy = $n;
if (!is_scalar($n)) {
$ncopy = gettype($ncopy);
/* (string)array() throws a notice on 5.4 */
} elseif ((string)$n === (string)(int)$n) {
$n = (int)$n;
return true;
} elseif ($keyMode && is_string($n)) {
return true;
}
trigger_error("Not a valid "
. ($keyMode ? 'array key' : 'integer') . ": '$ncopy'", E_USER_WARNING);
return false;
}
function conv_part($arr, $n, $keyMode)
{
if (!is_array($arr)) {
trigger_error("The depth of the part specification is too large",
E_USER_WARNING);
return false;
}
if (empty($arr)) {
trigger_error("Tried to access part of empty array", E_USER_WARNING);
return false;
}
if (!$keyMode) {
$count = count($arr);
if ($n >= 0) {
if ($n >= $count) {
trigger_error("Index '$n' is too large for the input", E_USER_WARNING);
return false;
}
return $n;
} else {
if (-($n + 1) >= count($arr)) {
trigger_error("Index '$n' is too large in absolute value "
. "for the input", E_USER_WARNING);
return false;
}
return count($arr) + $n;
}
} else {
if ($n === false) {
return 0;
}
if ($n === true) {
return count($arr) - 1;
}
$keys = array_keys($arr);
$search = array_search($n, $keys, true);
if ($search === false) {
trigger_error("Index '$n' not found in input", E_USER_WARNING);
}
return $search;
}
}
function do_unit($read, &$write, &$newReadTargets, &$newWriteTargets,
$index, $nextType)
{
$value = array_values($read)[$index];
if ($nextType === 'none') {
$write[] = $value;
} elseif ($nextType === 'multiple') {
$newSubArray = array();
$write[] =& $newSubArray;
$newReadTargets[] = $value;
$newWriteTargets[] =& $newSubArray;
} else { //single
$newReadTargets[] = $value;
$newWriteTargets[] =& $write;
}
}
function span_is_all($start, $end, $step, $keyMode)
{
return ($step === 1 &&
(($keyMode && $start === false && $end === true)
||
(!$keyMode && $start === 0 && $end === -1)))
||
($step === -1 &&
(($keyMode && $start === true && $end === false)
||
(!$keyMode && $start === -1 && $end === 0)));
}
function do_work_span(&$readTargets, &$writeTargets,
$start, $end, $step, $type, $keyMode)
{
$newReadTargets = array();
$newWriteTargets = array();
if (($count = count($readTargets)) !== count($writeTargets)) {
trigger_error("Logic error in the algorithm", E_USER_ERROR);
}
for ($i = 0; $i < $count; $i++) {
$r = $readTargets[$i];
$w =& $writeTargets[$i];
/* As an exception, if we request all elements allow empty arrays */
if ($r === array() && span_is_all($start, $end, $step, $keyMode)) {
continue;
}
$s = conv_part($r, $start, $keyMode);
$e = conv_part($r, $end, $keyMode); /* inclusive */
if ($s === false || $e === false)
return false;
for ($j = $s;
$step > 0 && $j <= $e || $step < 0 && $j >= $e;
$j += $step)
{
do_unit($r, $w, $newReadTargets, $newWriteTargets, $j, $type);
}
}
$readTargets = $newReadTargets;
$writeTargets = $newWriteTargets;
return true;
}
function do_work_indexes(&$readTargets, &$writeTargets,
$indexes, $type, $keyMode)
{
$newReadTargets = array();
$newWriteTargets = array();
if (($count = count($readTargets)) !== count($writeTargets)) {
trigger_error("Logic error in the algorithm", E_USER_ERROR);
}
for ($i = 0; $i < $count; $i++) {
$r = $readTargets[$i];
$w =& $writeTargets[$i];
foreach ($indexes as $ind) {
$j = conv_part($r, $ind, $keyMode);
if ($j === false)
return false;
do_unit($r, $w, $newReadTargets, $newWriteTargets, $j, $type);
}
}
$readTargets = $newReadTargets;
$writeTargets = $newWriteTargets;
return true;
}
function do_work_single_index(&$readTargets, &$writeTargets,
$index, $type, $keyMode)
{
$newReadTargets = array();
$newWriteTargets = array();
if (($count = count($readTargets)) !== count($writeTargets)) {
trigger_error("Logic error in the algorithm", E_USER_ERROR);
}
for ($i = 0; $i < $count; $i++) {
$r = $readTargets[$i];
$w =& $writeTargets[$i];
$j = conv_part($r, $index, $keyMode);
if ($j === false) {
return false;
}
// var_dump('pre_do_unit', $r, $w);
do_unit($r, $w, $newReadTargets, $newWriteTargets, $j, $type);
}
$readTargets = $newReadTargets;
$writeTargets = $newWriteTargets;
return true;
}
function array_part($arr, array $definition, $keyMode = false)
{
$result = array();
$readTargets = array($arr);
$firstPart = reset($definition);
if (is_array($firstPart)) {
$result[] = array();
$writeTargets = array(&$result[0]);
} else {
$writeTargets = array(&$result);
}
for ($depth = 0;
($partSpec = current($definition)) !== false;
$depth++) {
$nextPartSpec = next($definition);
if ($nextPartSpec === false)
$nextPartSpec = 'none';
elseif (is_array($nextPartSpec))
$nextPartSpec = 'multiple';
else
$nextPartSpec = 'single';
// var_dump($readTargets, $writeTargets, $nextPartSpec);
if (is_array($partSpec)) {
if (key_exists('start', $partSpec) ||
key_exists('end', $partSpec)) {
$start = $keyMode ? false /* special value */ : 0;
$end = $keyMode ? true /* special value */ : -1;
$step = 1;
foreach ($partSpec as $k => $v) {
if ($v !== null && !num_val($v, $keyMode)) {
return false;
}
if ($k === 'start') {
if ($v === null) {
continue; /* allow null default to 1st element */
}
$start = $v;
} elseif ($k === 'end') {
if ($v === null) {
continue; /* allow null default to last element */
}
$end = $v;
} elseif ($k === 'step') {
if (!num_val($v, false)) {
return false;
}
$step = $v;
if ($step === 0) {
trigger_error("Step cannot be 0", E_USER_WARNING);
return false;
}
} else {
trigger_error("Span definitions should only include "
. "elements with keys 'start', 'end' and 'step', "
. "given '$k", E_USER_WARNING);
return false;
}
}
if ($step < 0) {
/* revert defaults for start and end
* if we're moving backwards */
if ($start === false) {
$start = true;
}
if ($end === true) {
$end = false;
}
}
if (!do_work_span($readTargets, $writeTargets,
$start, $end, $step, $nextPartSpec, $keyMode)) {
return false;
}
} else {
$previousKey = -1;
foreach ($partSpec as $k => $v) {
if ($previousKey + 1 !== $k) {
trigger_error("Part definition at depth "
. ($depth + 1) . " is not a sequential array");
}
if (!num_val($v, $keyMode)) {
return false;
}
}
if (!do_work_indexes($readTargets, $writeTargets,
$partSpec, $nextPartSpec, $keyMode)) {
return false;
}
}
} else {
if (!num_val($partSpec, $keyMode)) {
return false;
}
if (!do_work_single_index($readTargets, $writeTargets,
$partSpec, $nextPartSpec, $keyMode))
return false;
}
}
return $result[0];
}
<?php
include "source.php";
$array = [
[1, 2, 'foo' => 3],
['a', 'foo' => 'b', 'c', 'd'],
];
echo 'All, {-1}', "\n";
print_r(
array_part($array, [
['start'=>0, 'end'=>-1],
[-1],
])
);
/*
[
[3],
['d'],
]
*/
echo 'All, -1', "\n";
print_r(
array_part($array, [
['start'=>0, 'end'=>-1],
-1,
])
);
/*
[3, 'd']
*/
echo "\n", '0, -1', "\n";
print_r(
array_part($array, [
0,
-1,
])
);
/*
3
*/
echo "\n", '{0}, -1', "\n";
print_r(
array_part($array, [
[0],
-1,
])
);
/*
[3]
*/
echo "\n", 'All, -2;;', "\n";
print_r(
array_part($array, [
['start'=>0, 'end'=>-1],
['start'=>-2, 'end'=>-1],
])
);
/*
[
[2, 3],
['c', 'd']
]
*/
echo "\n", 'All, "foo" (KEY MODE)', "\n";
print_r(
array_part($array, [
['start'=>null, 'end'=>null],
"foo",
], true)
);
/*
[3, 'b']
*/
echo "\n", 'All, {"foo"->START, -1} (KEY MODE)', "\n";
print_r(
array_part($array, [
['start'=>null, 'end'=>null],
['start' => "foo", 'step' => -1],
], true)
);
/*
[
[3, 2, 1],
['b', 'a'],
]
*/
echo "\n", 'Empty array: All, {-1}', "\n";
print_r(
array_part([], [
['start'=>0, 'end'=>-1],
[-1],
])
);
/*
[]
*/
echo "\n", '[[1,2],[]]: {-1}, All', "\n";
print_r(
array_part([[1,2], []], [
[-1],
['start'=>0, 'end'=>-1],
])
);
/*
[[]]
*/
echo "\n", '[[[1,2]],[]]: All, All, All (KEY MODE)', "\n";
print_r(
array_part([[[1,2]], []], [
['start'=>null],
['start'=>null],
['start'=>null],
], true)
);
/*
original array
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment