phparraysarray-splicepreserve

array_splice preserving keys


I faced the situation that splicing arrays with preserved-keys, so I made the following function.
I reached the solution that wrapping each items with array, but there seems to be some memory-inefficient statements.
Have you any ideas?
Thank you.


array_splice_pk

This preserves keys, differently from array_splice.

Overview:

Code:

function array_splice_pk(&$input, $key, $use_key_as_offset = false, $length = 0, $replacement = null) {
    if (!is_array($input) || !is_scalar($key)) {
        return array();
    }
    if ($replacement !== null) {
        $replacement = array($replacement);
        if (!is_array($replacement[0])) {
            $replacement = array($replacement);
        }
    }
    $wrapper = array();
    foreach ($input as $k => $v) {
        $wrapper[] = array($k => $v);
    }
    $del_key = null;
    foreach ($wrapper as $k => $v) {
        if ($use_key_as_offset) {
            if ($k === (int)$key) {
                $del_key = $k;
                break;
            }
        } else {
            if (key($v) == $key) {
                $del_key = $k;
                break;
            }
        }
    }
    if ($del_key === null) {
        return array();
    }
    if ($replacement === null) {
        $wrapper_ret = array_splice($wrapper, $del_key, $length);
    } else {
        $wrapper_ret = array_splice($wrapper, $del_key, $length , $replacement);
    }
    $ret = $input = array();
    foreach ($wrapper_ret as $wrap) {
        list($k, $v) = each($wrap);
        $ret[$k] = $v;
    }
    foreach ($wrapper as $wrap) {
        list($k ,$v) = each($wrap);
        $input[$k] = $v;
    }
    return $ret;
}

Sample:

$arr1 = $arr2 = array(
    'one'   => 'test',
    'two'   => 'test',
    'three' => 'test',
    'four'  => 'test',
);
$ret1 = array_splice_pk($arr1, 'three', false, 1, array('fizz' => '!!!'));
$ret2 = array_splice_pk($arr2, 2      , true , 1, array('fizz' => '!!!'));

var_dump('Result1', $arr1, $ret1, 'Result2', $arr2, $ret2);

Result:

string(7) "Result1"
array(4) {
  ["one"]=>
  string(4) "test"
  ["two"]=>
  string(4) "test"
  ["fizz"]=>
  string(3) "!!!"
  ["four"]=>
  string(4) "test"
}
array(1) {
  ["three"]=>
  string(4) "test"
}
string(7) "Result2"
array(4) {
  ["one"]=>
  string(4) "test"
  ["two"]=>
  string(4) "test"
  ["fizz"]=>
  string(3) "!!!"
  ["four"]=>
  string(4) "test"
}
array(1) {
  ["three"]=>
  string(4) "test"
}

Solution

  • I'll post the self-answer with my PHP 8 knowledge in 2022.
    It correctly accepts negative length/offset and string offset.

    function array_splice_assoc(array &$input, int|string $key, ?int $length = null, $replacement = [], bool $use_int_key_as_offset = true): array
    {
        // Normalize key/offset
        $offset = match (true) {
            is_string($key) || !$use_int_key_as_offset => array_flip(array_keys($input))[$key] ?? throw new OutOfBoundsException(),
            $key < 0 => count($input) + $key,
            default => $key,
        };
    
        // Normalize length
        $length = match (true) {
            $length === null => count($input) - $offset,
            $length < 0 => count($input) + $length - $offset,
            default => $length,
        };
    
        // Manipulate each part
        $before = array_slice($input, 0, $offset, true);
        $removed = array_slice($input, $offset, $length, true);
        $after = array_slice($input, $offset + $length, null, true);
    
        // Merge parts, allowing the latter overrides the former
        $input = array_replace($before, (array)$replacement, $after);
    
        return $removed;
    }
    

    Examples:

    $array = ['a' => 'A', 'b' => 'B', 3 => 'C', 4 => 'D'];
    
    $original = $array;
    $removed = array_splice_assoc($original, 1, 1, [5 => 'E']);
    echo json_encode(compact('original', 'removed')) . PHP_EOL;
    /*
    {"original":{"a":"A","5":"E","3":"C","4":"D"},"removed":{"b":"B"}}
    */
    
    $original = $array;
    $removed = array_splice_assoc($original, 2, replacement: [5 => 'E']);
    echo json_encode(compact('original', 'removed')) . PHP_EOL;
    /*
    {"original":{"a":"A","b":"B","5":"E"},"removed":{"3":"C","4":"D"}}
    */
    
    $original = $array;
    $removed = array_splice_assoc($original, -3, 1, [5 => 'E']);
    echo json_encode(compact('original', 'removed')) . PHP_EOL;
    /*
    {"original":{"a":"A","5":"E","3":"C","4":"D"},"removed":{"b":"B"}}
    */
    
    $original = $array;
    $removed = array_splice_assoc($original, -3, -1, [5 => 'E']);
    echo json_encode(compact('original', 'removed')) . PHP_EOL;
    /*
    {"original":{"a":"A","5":"E","4":"D"},"removed":{"b":"B","3":"C"}}
    */
    
    $original = $array;
    $removed = array_splice_assoc($original, 'b', 2, [5 => 'E']);
    echo json_encode(compact('original', 'removed')) . PHP_EOL;
    /*
    {"original":{"a":"A","5":"E","4":"D"},"removed":{"b":"B","3":"C"}}
    */
    
    $original = $array;
    $removed = array_splice_assoc($original, 3, 1, [5 => 'E']);
    echo json_encode(compact('original', 'removed')) . PHP_EOL;
    /*
    {"original":{"a":"A","b":"B","3":"C","5":"E"},"removed":{"4":"D"}}
    */
    
    $original = $array;
    $removed = array_splice_assoc($original, 3, 1, [5 => 'E'], false);
    echo json_encode(compact('original', 'removed')) . PHP_EOL;
    /*
    {"original":{"a":"A","b":"B","5":"E","4":"D"},"removed":{"3":"C"}}
    */